Search code examples
rustrust-crates

is there a way return different types of a struct in the same function?


I'm trying to use the nannou crate to randomly generate symbols, but I'm having trouble writing a function that randomly generates a symbol and stores it into the following struct

struct Sigil<T,U>{
    major:Drawing<'static,T>,
    minor:Vec<Drawing<'static,U>>
}

i tried to do the following

fn sigil_make<T,U>(app: &App)->Sigil<T,U>{
    let draw=app.draw();
    let mut sigil:Sigil<T,U>;
    match random::<u8>()%2 {
        0=>sigil.major=draw.ellipse(),
        1=>sigil.major=draw.polygon(),
        _=>println!("uh oh")
    }
    return sigil;
}

but this doesn't work as major is expecting type T while I'm trying to set it to a shape from nannou.

what would be the appropriate way to go about doing this?


Solution

  • There's several problems all at once here. I will list them along with the possible solutions.

    • You allow the caller to specify the type T, but your function already knows the type itself.
      • Simply omit the T parameter. (It may make sense for the caller to specify what U is since your function does not.)
    • The two methods you are calling return different types, but Rust is statically typed and needs a single type.
      • Returning one type or the other at runtime requires either dynamic dispatch (usually seen as Box<dyn _> with a trait) or an "either" type, such as an enum with a variant for each possible returned type.
    • These two methods also capture the lifetime of the Draw returned by app.draw(), but the Draw will be destroyed when the function returns.
      • Accept a &Draw instead of an &App, which will give you a longer-lived Draw to borrow from.
    • The _ arm of your match needs to evaluate as something of the same type as well.
      • Have this arm panic, or return an Option. Since another value should not be possible, we can panic by using the built-in unreachable! macro.
      • Or, change the 1 arm to _.
    • You partially-initialize the Sigil; the minor field is not initialized and so you cannot return it.
      • Initialize minor as well.
    • Sigil specifies that the lifetimes of the Drawing values are 'static, which is not guaranteed to be true.
      • Add a lifetime to Sigil.

    Here's an example of how you could solve these issues:

    struct Sigil<'a, T, U> {
        major: Drawing<'a, T>,
        minor: Vec<Drawing<'a, U>>,
    }
    
    // See also the "either" crate, which provides an enum like this.
    enum Either<L, R> {
        Left(L),
        Right(R),
    }
    
    fn sigil_make<U>(draw: &Draw) -> Either<Sigil<'_, Ellipse, U>, Sigil<'_, PolygonInit, U>> {
        match random::<u8>() % 2 {
            0 => Either::Left(Sigil {
                major: draw.ellipse(),
                minor: vec![],
            }),
            _ => Either::Right(Sigil {
                major: draw.polygon(),
                minor: vec![],
            }),
        }
    }