It probably amounts to the same thing, but I think there's a more pragmatic approach to thinking about safety. A type is a way to remember that validation has already been done. This is true for constants by inspection (code review). For dynamic code, have a validator function that takes unvalidated input and returns a Color or an error, and always use that to create a Color.
That's usually sufficient. Any "cheating" should come up in code review as a suspicious cast to Color. In an audit, you could search for casts to Color.
Safe languages often have unsafe constructs. It's the same principle. The unsafe code is signposted, and you review it.
If you want further encapsulation, another useful trick is to make Color a struct with a private field. It's not usually necessary, though.
Go does have an unfortunate quirk that you can always create a zero value without calling a constructor, so you'll need to make sure a zero Color has meaning. (An interface doesn't really change this because the then the zero value is nil. That's not an improvement over making the zero value mean "black" or "transparent" or "invalid".)
That's usually sufficient. Any "cheating" should come up in code review as a suspicious cast to Color. In an audit, you could search for casts to Color.
Safe languages often have unsafe constructs. It's the same principle. The unsafe code is signposted, and you review it.
If you want further encapsulation, another useful trick is to make Color a struct with a private field. It's not usually necessary, though.
Go does have an unfortunate quirk that you can always create a zero value without calling a constructor, so you'll need to make sure a zero Color has meaning. (An interface doesn't really change this because the then the zero value is nil. That's not an improvement over making the zero value mean "black" or "transparent" or "invalid".)