Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Box a trait that has associated types?

I'm very new to Rust so I may have terminology confused.

I want to use the hashes crates to do some hashing and I want to dynamically pick which algorithm (sha256, sha512, etc.) to use at runtime.

I'd like to write something like this:

let hasher = match "one of the algorithms" {
    "sha256" => Box::new(Sha256::new()) as Box<Digest>,
    "sha512" => Box::new(Sha512::new()) as Box<Digest>
    // etc...
};

I sort of get that that doesn't work because the associated types required by Digest aren't specified. If I attempt to fill them in:

"sha256" => Box::new(Sha256::new()) as Box<Digest<<OutputSize = U32, BlockSize = U64>>>,

I'm left with an error: the trait 'digest::Digest' cannot be made into an object. I think this approach will fail anyway because match will be returning slightly different types in cases where different algorithms have different associated types.

Am I missing something obvious? How can I dynamically create an instance of something that implements a trait and then hold on to that thing and use it through the trait interface?

like image 586
WestleyArgentum Avatar asked Dec 29 '17 20:12

WestleyArgentum


People also ask

What is an associated type?

An associated type gives a placeholder name to a type that's used as part of the protocol. The actual type to use for that associated type isn't specified until the protocol is adopted. Associated types are specified with the associatedtype keyword.

What are generic associated types?

GATs (generic associated types) were originally proposed in RFC 1598. As said before, they allow you to define type, lifetime, or const generics on associated types. If you're familiar with languages that have "higher-kinded types", then you could call GATs type constructors on traits.

What are associated types in Rust?

Associated Types in Rust are similar to Generic Types; however, Associated Types limit the types of things a user can do, which consequently facilitates code management. Among the Generic Types of traits, types that depend on the type of trait implementation can be expressed by using the Associated Type syntax.

Can traits have fields Rust?

Traits can't have fields. If you want to provide access to a field from a trait, you need to define a method in that trait (like, say, get_blah ).


1 Answers

The message refers to object safety (longer article). The Digest trait has two incompatibilities:

  1. It uses associated types (this can be worked around by explicitly setting all type parameters to values compatible for all Digest objects).
  2. It has a method (fn result(self) -> …) taking self by value. You won't be able to call it, which ruins usability of this trait.

Once a trait object is created, information about its subtype-specific features such as memory layout or associated types is erased. All calls to the trait object's methods are done via a vtable pointer. This means they all must be compatible, and Rust can't allow you to call any methods that could vary in these aspects.

A workaround is to create your custom wrapper trait/adapter that is object-compatible. I'm not sure if that's the best implementation, but it does work:

trait Digest {
    type Assoc;
    fn result(self);
}

struct Sha;

impl Digest for Sha {
    type Assoc = u8;
    fn result(self) {}
}

///////////////////////////////////////////

trait MyWrapper {
    fn result(&mut self); // can't be self/Sized
}

impl<T: Digest> MyWrapper for Option<T> {
    fn result(&mut self) {
        // Option::take() gives owned from non-owned
        self.take().unwrap().result() 
    }
}

fn main() {
    let mut digest: Box<MyWrapper> = Box::new(Some(Sha));
    digest.result();
}
like image 87
Kornel Avatar answered Sep 24 '22 05:09

Kornel