Returning a impl trait that has generic

Hi,

Is there away to have a function returning impl Trait when Trait has generic type?

I'm trying to return an impl of this trait BlockMode in block_modes - Rust

according to a str

Something like this:

fn get_block_mode(s: &str) -> impl BlockMode {
    match s {
         "AES-128-CFB" => Aes128Cfb::new_from_slices(...),
         "AES-256-CBC" => Aes256Cbc::new_from_slices(...),
    }
}

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.

Thank you for your answer!

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

Where the trait and method in question are:

So, my first reaction would be for you to write your own non-generic trait:

trait BlockModeDecrypt {
    fn decrypt (self: Self, buffer: &mut [u8])
      -> Result<&[u8], BlockModeError>
    where
        Self : Sized, /* required for `self` receiver */
    ;
}

And then we:

  • 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. :x:

  • Or we write impls of this trait for the two specific types in question: Aes128Cfb and Aes128Cfb. :ok:

The second option works, ..., but is a bit cumbersome :grimacing::

  1. define a trait,
  2. write the impl for the two specific types,
  3. try to unify those two types using Box<dyn …>,
  4. 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);
  5. Replace self: Self with self: Box<Self>, so as to remove all the Sized bounds, and make it work.

image

Let's stare at BlockModeDecrypt again, shall we?

trait BlockModeDecrypt {
    fn decrypt (self: Box<Self>, buffer: &mut [u8])
      -> Result<&[u8], BlockModeError>
    ;
}

keep staring…

image

This simplifies both cumbersome things in one go:

  • Box<dyn FnOnce(…) -> …> Just Works™, no need to worry about dyn;

  • Wrapping stuff so that it implements this trait can be done in one line!

type DynDecryptFunction =
    dyn 'static +
        Send + 
        FnOnce(&mut [u8]) -> Result<&[u8], BlockModeError> +
;

fn get_block_mode_decrypt_function (s: &'_ str)
  -> Box<DynDecryptFunction>
{
    match s {
        | "AES-128-CFB" => {
            let aes_128_cfb = Aes128Cfb::new_from_slices(...);
            Box::new(move |buf| aes_128_cfb.decrypt(buf))
        },
        | "AES-256-CBC" => {
            let aes_256_cbc = Aes256Cbc::new_from_slices(...);
            Box::new(move |buf| aes_256_cbc.decrypt(buf))
        },
        | _ => todo!("Handle other strings"),
    }
}
4 Likes

I knew it that there was an elegant Rust way to do it but I'm too thick to find it!! I'll test that on Monday (it on work's code) :smiley:
Thanks a lot!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.