What about fn get_block_mode(s: &str) -> impl BlockMode<SomeBlockCipher, SomePadding>?
Keep in mind that returning impl Trait is just a short cut so you don't need to write the full type name, so Aes128Cfb::new_from_slices() and Aes256Cbc::new_from_slices() both need to return the exact same type.
That the thing, they are slightly different, but all I want after calling get_block_mode it to access BlockMode::decrypt() which does not need any of the generic information
either write an blanket impl: "any implementor of BlockMode<…> trivially implements my BlockModeDecrypt". But there is an actual flaw in the design of BlockMode: it's using generic type parameters instead of associated types! This means that a concrete type could theoretically implement multiple BlockModes! And this means that the blanket impl could yield overlapping impls, which goes against coherence. In practice, this will yield an "unconstrained type parameter" kind of error, which one can solve by making our own trait generic over those type parameters … and we are back to square one.
Or we write impls of this trait for the two specific types in question: Aes128Cfb and Aes128Cfb.
The second option works, ..., but is a bit cumbersome :
define a trait,
write the impl for the two specific types,
try to unify those two types using Box<dyn …>,
observe that the self: Self receiver either leads to a dyn-incompatible trait if the Sized bound is on the trait itself, or to an uncallable .decrypt() method on Box<dyn …> if it is on the method (as showcased above);
Replace self: Self with self: Box<Self>, so as to remove all the Sized bounds, and make it work.