Color3:ToHex() can error

Reproduction Steps

Title basically says it all. I should not have to pcall this basic function. If the values are out of bounds, clamp it.

Repro: Put this in the command bar.

=Color3.fromRGB(500,100,-10):ToHex()
Unable to convert color to valid hex code
Stack Begin
Script ‘print(Color3.fromRGB(500,100,-10):ToHex())’, Line 1
Stack End

Expected Behavior

Color3:ToHex() should just work. Clamp the values, but it is very unintuitive and annoying to need a pcall on this basic utility function.

Actual Behavior

Unable to convert color to valid hex code
Stack Begin
Script ‘print(Color3.fromRGB(500,100,-10):ToHex())’, Line 1
Stack End

Workaround

Manually clamping each channel before constructing a new Color3 object to call ToHex on.

Issue Area: Engine
Issue Type: Other
Impact: Low
Frequency: Sometimes

5 Likes

This looks intentional as it has its own error message. I think this might be more fitting for an engine feature request, but I do not think that Roblox will do anything about this issue as colours should not extend past 255, 255, 255.

Since #FFFFFF is the max and #000000 is the min; it would make sense to clamp it manually. When converting a hex code from user input to use for something else; I always clamp it using math.clamp(SingleRGBValue,0,255) anyways. I understand it may be annoying that the engine doesn’t automatically do that, but it’s something that really doesn’t need fixing.

2 Likes

It’s not about it being the most useful feature, it’s just about quality of life. This would be especially useful if you have a dynamic system of sorts for creating hex codes, it’s just nicer to automatically have it be clamped.

I’m not too sure if this should be something they should implement, it shouldn’t “just work” as it leads to confusing logic being executed, for example:

Color3.fromRGB(500,100,-10):ToHex()

That’s direct and obvious as to the problem, however…

Color3.fromRGB(10, 10, -10):ToHex()

Isn’t so obvious, and could easily be a mistake on the developers end, and therefore this error would bring this problem to light, but if we included behind the scenes magic to clamp these values, well finding this error might not be so easy…


The workaround you provided should suffice for what you’re trying to do? If you need to keep writing it up and you’re tired of doing so, then add it to a Utils module or make a module for it…

Color3:ToHSV() does not error with an invalid input color, while Color3:ToHex() does. This an inconsistency in the API. And, the precedent with basic data types like these is that they never throw errors in these kinds of situations (e.g. consider Vector3.Unit giving you NaN result when used on the zero vector).

3 Likes

Color3:ToHSV() does not error with an invalid input color, while Color3:ToHex() does. This an inconsistency in the API

Yeah I agree that the inconsistency is an issue, if that was brought up in the initial ticket I would also sided with that opinion too.

the precedent with basic data types like these is that they never throw errors in these kinds of situations

Is there a document or something to back this up? If so then that’s something on my end I would happily take up on looking into, but I do honestly think it does create a level of obscurity which doesn’t need to be there…

Then again I don’t see the issue with clamping the RGB Values passed into these methods either…

but it is very unintuitive and annoying to need a pcall on this basic utility function.

Shouldn’t be a reason to create an obscurity?

There is some justification for trend - in general, basic datatypes are often used in situations where performance is important (e.g. manipulating hundreds or thousands of Vector3s in a single frame).

Forcing programmers to write error handling code (such as wrapping API calls in pcalls) or validation code (clamping values before using the APIs that can error) for each individual operation is just a waste of performance.

Programmers might still want to detect invalid values occasionally, but the default behavior should be that everything just executes safely and you get a result at the end.

Also, throwing an error can be game-breaking, while just giving a garbage result is less likely to make things explode. (e.g. your hex color picker stops working completely vs your hex color picker displays the wrong color if you type in garbage input)

There is some justification for trend - in general, basic datatypes are often used in situations where performance is important

Sure, though would Color3 fit into the “basic datatypes” category?

for each individual operation is just a waste of performance.

I’d argue it’s how you handle these values, they should already be sanitized before being put through what could be a stressful process? In the case where we’re unable to do such, will it not still have an impact either or since the engine will do the same process?

but the default behavior should be that everything just executes safely and you get a result at the end.

Why? I feel like this is just creating obscurity in the way we call methods? How am I to tell if something didn’t work if it didn’t error? A visual change which I might not catch? An error is much more direct and exposes the problem at hand, but making this API forgiving hides that solidarity

Also, throwing an error can be game-breaking

That, well that is the point? If RGB values are not met, then I would also throw an error ~ i’m not sure but this does create obscurity in the definition of how to execute something?

while just giving a garbage result is less likely to make things explode.

Should your systems not account for this? They do atm sure, but they should continue to account for such inputs as if we don’t it may lead us


Some of these points may seem off topic, or targeted towards a different subject, but this could be the start of many different changes which may make the engine too forgiving…

We should want the engine to be more demanding if anything…