Do I necessarily need to Box here?

2019-07-25 16:09发布

Is there any way to make this code work without using Boxing:

fn some_func(my_type: MyType, some_str: &str) -> bool {
  let mut hmac = match my_type {
    MyType::MyType1 => create_hmac(Sha256::new(), some_str),
    MyType::MyType2 => create_hmac(Sha384::new(), some_str),
    MyType::MyType3 => create_hmac(Sha512::new(), some_str),
    _ => panic!()
  };

  //some calculations goes HERE, NOT in create_hmac function...
  hmac.input("fdsfdsfdsfd".to_string().as_bytes());

  //something else....
  true
}

fn create_hmac<D: Digest>(digest: D, some_str: &str) -> Hmac<D> {
  Hmac::new(digest, some_str.to_string().as_bytes())
}

The library it's using is https://github.com/DaGenix/rust-crypto

标签: rust
2条回答
淡お忘
2楼-- · 2019-07-25 16:29

You need to either Box or use a reference, as a "trait object" can only work behind a pointer.

Here's a very simplified version of your code. You have three different structs that implement the same trait (Digest)

struct Sha256;
struct Sha384;
struct Sha512;

trait Digest {}
impl Digest for Sha256 {}
impl Digest for Sha384 {}
impl Digest for Sha512 {}

struct HMac<D: Digest> { d: D }

fn main() {
    let a = 1;

    // what you're trying to do
    // (does not work, Sha256, Sha384 and Sha512 are different types)
    //let _ = match a {
    //    1 => Sha256,
    //    2 => Sha384,
    //    3 => Sha512,
    //    _ => unreachable!()
    //};
}

Note that in the real case, not only all ShaXXX types are different for the type system, they have a different memory layout as well (compare Engine256State with Engine512State for instance), so this rules out unsafe tricks with transmute.

So, as said, you can use Box or references (but you have to pre-create a concrete instance before the match if you want to use references):

fn main() {
    let a = 1;

    let _ : Box<Digest> = match a {
        1 => Box::new(Sha256),
        2 => Box::new(Sha384),
        3 => Box::new(Sha512),
        _ => unreachable!()
    };

    // to use references we need a pre-existing instance of all ShaXXX
    let (sha256, sha384, sha512) = (Sha256, Sha384, Sha512);

    let _ : &Digest = match a {
        1 => &sha256, //... otherwise the reference wouldn't outlive the match 
        2 => &sha384,
        3 => &sha512,
        _ => unreachable!()
    };
}

Note that a Box is doing the equivalent of what most Garbage Collected languages do for you under the hood when you want to only use an object through its interface. Some memory is dynamically allocated for the concrete objects, but you're only really allowed to pass around a pointer to the memory.

In your case (but I haven't tested the code below) you should be able to do:

//HMac implements a Mac trait, so we can return a Box<Mac>
// (I'm assuming you only want to use HMac through its Mac trait)
fn create_hmac<'a, D: Digest>(digest: D, some_str: &'a str) -> Box<Mac + 'a> {
  Box::new(Hmac::new(digest, some_str.to_string().as_bytes()))
}

and use it as:

  let mut hmac: Box<Mac> = match my_type {
    MyType::MyType1 => create_hmac(Sha256::new(), some_str),
    MyType::MyType2 => create_hmac(Sha384::new(), some_str),
    MyType::MyType3 => create_hmac(Sha512::new(), some_str),
    _ => unreachable!()
  };
查看更多
何必那么认真
3楼-- · 2019-07-25 16:41

One addition and one clarification to Paolo's good answer. First, you could make your enum incorporate the appropriate Sha* struct and then implement Digest by delegating it as appropriate. This might not make sense in all cases, but if conceptually that's what you are doing this might make sense:

struct Sha256;
struct Sha384;
struct Sha512;

trait Digest { fn digest(&self); }
impl Digest for Sha256 { fn digest(&self) {println!("256")} }
impl Digest for Sha384 { fn digest(&self) {println!("384")} }
impl Digest for Sha512 { fn digest(&self) {println!("512")} }

enum MyType {
    One(Sha256),
    Two(Sha384),
    Three(Sha512),
}

impl Digest for MyType {
    fn digest(&self) {
        use MyType::*;

        match *self {
            One(ref sha)   => sha.digest(),
            Two(ref sha)   => sha.digest(),
            Three(ref sha) => sha.digest(),
        }
    }
}

fn main() {
    let a = MyType::Two(Sha384);
    a.digest()
}

Also, you don't have to actually instantiate all of the types if you want to use references, you just have to ensure that the one you use is available. You also have to have places where the reference can live beyond the match expression:

#![feature(std_misc)]
#![feature(io)]

use std::time::duration::Duration;
use std::old_io::timer::sleep;

struct Sha256(u8);
struct Sha384(u8);
struct Sha512(u8);

impl Sha256 { fn new() -> Sha256 { sleep(Duration::seconds(1)); Sha256(1) }}
impl Sha384 { fn new() -> Sha384 { sleep(Duration::seconds(2)); Sha384(2) }}
impl Sha512 { fn new() -> Sha512 { sleep(Duration::seconds(3)); Sha512(3) }}

trait Digest {}
impl Digest for Sha256 {}
impl Digest for Sha384 {}
impl Digest for Sha512 {}

fn main() {
    let a = 1;

    let sha256: Sha256;
    let sha384: Sha384;
    let sha512: Sha512;

    let _ : &Digest = match a {
        1 => {
            sha256 = Sha256::new();
            &sha256
        },
        2 => {
            sha384 = Sha384::new();
            &sha384
        },
        3 => {
            sha512 = Sha512::new();
            &sha512
        },
        _ => unreachable!()
    };
}
查看更多
登录 后发表回答