You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When a fn returns a tuple of Digest, it becomes necessary to read the fn implementation (or if lucky, docs) in order to determine what each Digest represents. Worse, the compiler does not know the logical type, and thus cannot enforce type safety beyond Digest.
The same applies for fn parameters. At least they are named, but sometimes may contain unnamed tuples, eg Vec<(Digest, ..)>.
So it would be a win for type-safety and maintainability if we were to strongly type these logical types. So then eg SenderRandomness can only be used where SenderRandomness is expected.
Unfortunately, rust does not make this a trivial task.
At first, one might think we could just use type aliases, eg:
type SenderRandomness = Digest;
This can indeed help readability, however the compiler does nothing to enforce type-safety. A SenderRandomness alias can be passed to a fn that expects a Digest and vice-versa. This is not a solution.
This works. The compiler will enforce type-safety for the newtype. However, to be fully backwards-compatible, the newtype must re-implement the entire public interface of Digest.
There is one possible shortcut, though it is frowned upon.
The newtype can impl Deref, and possibly DerefMut.
In that case, all methods from "impl Digest" are callable on SenderRandomness.
The downside is that we lose some type safety if a variable is passed by reference, eg a fn that with an &Digest param would happily accept an &SenderRandomness because the deref() method returns an &Digest.
In the case of Digest, it is Copy and is normally passed by value anyway. So that's not much of an issue. It is a code-smell though, and is not intended usage for Deref, which exists to support smart-pointer usage. Anyway its an option.
Digest presently only has about 6 methods and 1 public field. So not impractical to impl wrapper methods.
It also has 14 impl x for y and 12 derive() impls. The former can be impl'd in the newtype with a simple one-line wrapper for each.
So then, considering all the above:
My thought is to create a macro that impls the newtype pattern for each logical type that we wish to be distinct.
Existing code can then be mechanically transformed to use the newtype instead of Digest.
The text was updated successfully, but these errors were encountered:
Presently we have several logical types that all have the concrete type
Digest
.For example, these are all taken from grepping the code:
...and so on.
This can get particularly confusing when tuples are used, eg:
When a fn returns a tuple of Digest, it becomes necessary to read the fn implementation (or if lucky, docs) in order to determine what each Digest represents. Worse, the compiler does not know the logical type, and thus cannot enforce type safety beyond Digest.
The same applies for fn parameters. At least they are named, but sometimes may contain unnamed tuples, eg Vec<(Digest, ..)>.
So it would be a win for type-safety and maintainability if we were to strongly type these logical types. So then eg SenderRandomness can only be used where SenderRandomness is expected.
Unfortunately, rust does not make this a trivial task.
At first, one might think we could just use type aliases, eg:
This can indeed help readability, however the compiler does nothing to enforce type-safety. A SenderRandomness alias can be passed to a fn that expects a Digest and vice-versa. This is not a solution.
Thus developers typically use a NewType struct for this:
This works. The compiler will enforce type-safety for the newtype. However, to be fully backwards-compatible, the newtype must re-implement the entire public interface of Digest.
There is one possible shortcut, though it is frowned upon.
The newtype can impl Deref, and possibly DerefMut.
In that case, all methods from "impl Digest" are callable on SenderRandomness.
The downside is that we lose some type safety if a variable is passed by reference, eg a fn that with an &Digest param would happily accept an &SenderRandomness because the deref() method returns an &Digest.
In the case of Digest, it is
Copy
and is normally passed by value anyway. So that's not much of an issue. It is a code-smell though, and is not intended usage for Deref, which exists to support smart-pointer usage. Anyway its an option.Digest presently only has about 6 methods and 1 public field. So not impractical to impl wrapper methods.
It also has 14
impl x for y
and 12derive()
impls. The former can be impl'd in the newtype with a simple one-line wrapper for each.So then, considering all the above:
My thought is to create a macro that impls the newtype pattern for each logical type that we wish to be distinct.
Existing code can then be mechanically transformed to use the newtype instead of Digest.
The text was updated successfully, but these errors were encountered: