Depending on the selected hash algorithm, I want to create the matching instance of SigingKey from rsa::pss module and assign it to a variable for later use, like this:
let signing_key = match param.hash_algo {
HashAlgorithm::Sha2_256 => Some(SigningKey::<Sha256>::new(private_key)),
HashAlgorithm::Sha2_512 => Some(SigningKey::<Sha512>::new(private_key)),
/* ... */
_ => None
};
I get error:
match arms have incompatible types
How can this be worked around? I think it should be possible, because all types that might result from the match implement the rsa::signature::RandomizedSigner trait!
But this won't work either:
let signing_key: Option<Box<dyn RandomizedSigner>> = match param.hash_algo {
HashAlgorithm::Sha2_256 => Some(Box::new(SigningKey::<Sha256>::new(private_key))),
HashAlgorithm::Sha2_512 => Some(Box::new(SigningKey::<Sha512>::new(private_key))),
_ => None
};
I get error:
the trait RandomizedSigner cannot be made into an object
Your best bet is probably to define your own, trait object-safe trait. It needs to expose the method(s) you need to be able to call. Then, you implement that for the concrete types needed (presumably a blanket implementation involving T: RandomizedSigner). You should then be able to turn those values into Box<dyn SafeTrait>s.
To do anything with a dyn Any, you have to downcast to the specific concrete type it was originally created from. Which rather defeats the presumed goal here.
Edit: to be clear: you cannot downcast to a trait implemented by the type, it has to be the exact original type.
No, that's why I added the edit. The type. The exact type. Not a trait, not a field, the original type. That means a different concrete type for every single possible branch.
No, that's why I added the edit. The type. The exact type. Not a trait, not a field, the original type. That means a different concrete type for every single possible branch.
That's really unfortunate!
I have done a lot of Java and C# in the past, and there it is perfectly possible and perfectly normal that if we have objects of different types (classes) then we can assign them all to a single "common" variable, as long as they all implement the same interface. The type of the "common" variable will simply be that of the "shared" interface. We then can easily call any of the "shared" methods from the "common" variable, completely independent of what the concrete type is
Rust really has no "nicer" solution for this use-case? Should be pretty common...
I grew up on COM and .NET, I know how they work. Box<dyn SomeTrait> works just fine in Rust. The problem is that you're trying to use a trait that cannot be turned into a vtable. For example, if any method is generic it's excluded because generics have to be monomorphised, and that requires either runtime code generation or implicit dynamic dispatch, neither of which Rust is willing to do. Static methods are excluded because they can't be dispatched. Same with associated constants. Same with Self values. Java & C# don't have this problem either because they don't have these features, or because they're willing to force dynamic dispatch, or because they have a JIT.
That's why I said you need to define your own trait that fits within these limitations, and implement it for the types you want to use. Then dynamic dispatch works just fine.
Thing is, I did not create these traits. I'm just trying to use Rust Crypto's rsa module, as it exists. So I'm not to blame that these traits cannot be turned into vtable.
I understand that I probably could write my own traits to wrap the existing traits. But that feels more like a workaround than a proper solution. If that is the "best" solution that Rust has to offer for "my" use case – which should be a very common one – then this still is very unfortunate...
(especially considering that this would be a "no brainer" in pretty much any other OO language)
I'm not blaming you, just trying to get across that what you're trying to do doesn't work, and doesn't work for good reasons.
I mean, there's not a lot else Rust can really do. It could remove the incompatible features from traits entirely, but that would somewhat cripple the language. The compiler used to ignore incompatible parts of traits unless you tried to use them, but this led to people designing their code around dynamic dispatch of traits that couldn't meaningfully be used that way. It could abandon competing with C/C++ and grow a JIT runtime, but that seems really unlikely.
The one way it could be improved is if the language grew a feature that let you quickly "subset" a trait to just its dynamically dispatchable parts. That would definitely be useful to have. But since you can do that manually, I suspect it wouldn't get much traction when there are so many other more pressing limitations in the language.
Rust is not an object-oriented language. Trying to use it like one pretty much always results in pain and suffering. As you are discovering.
Taking a quick look at signature::RandomizedSigner, it looks like all you might need to do is copy the trait, change the &mut impl CryptoRngCore into &mut dyn CryptoRngCore, and then do a single blanket implementation.
The problem is likely those impl CryptoRngCores, since those are implicitly generic methods.
I'm not arguing against the technical reasons why things in Rust are like they are. Just trying to find a "clean" (minimal) solution for my use-case. Having to implement additional traits for wrapping the existing traits/structs, just to do something that should be (and in most languages is) a one-liner, doesn't exactly count as a "clean" (minimal) solution, IMHO
I have now found that even though it seems to be impossible to store an instance of RandomizedSigner<Signature> in a variable, we still can pass it as a function parameter. So, after all, we can "emulate" the variable with function parameter:
Just as a note, you're not writing a single function when you write signer: impl RandomizedSigner… - you're telling Rust that you want it to create one instance of this function for every unique implementation of RandomizedSigner that's passed to it.
This is monomorphization, and the equivalent transform is done by the JIT in C# and Java.