Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Explicit lifetime error in rust

I have a rust enum that I want to use, however I recieve the error;

error: explicit lifetime bound required
numeric(Num),
        ~~~

The enum in question:

enum expr{
   numeric(Num),
   symbol(String),
}

I don't think I understand what is being borrowed here. My intent was for the Num or String to have the same lifetime as the containing expr allowing me to return them from functions.

like image 950
ragingSloth Avatar asked Feb 12 '23 10:02

ragingSloth


1 Answers

The error message is somewhat misleading. Num is a trait and it is a dynamically sized type, so you can't have values of it without some kind of indirection (a reference or a Box). The reason for this is simple; just ask yourself a question: what size (in bytes) expr enum values must have? It is certainly at least as large as String, but what about Num? Arbitrary types can implement this trait, so in order to be sound expr has to have infinite size!

Hence you can use traits as types only with some kind of pointer: &Num or Box<Num>. Pointers always have fixed size, and trait objects are "fat" pointers, keeping additional information within them to help with method dispatching.

Also traits are usually used as bounds for generic type parameters. Because generics are monomorphized, they turn into static types in the compiled code, so their size is always statically known and they don't need pointers. Using generics should be the default approach, and you should switch to trait objects only when you know why generics won't work for you.

These are possible variants of your type definition. With generics:

enum Expr<N: Num> {
    Numeric(N),
    Symbol(String)
}

Trait object through a reference:

enum Expr<'a> {  // '
    Numeric(&'a Num + 'a),
    Symbol(String)
}

Trait object with a box:

enum Expr {
    Numeric(Box<Num + 'static>),  // ' // I used 'static because numbers usually don't contain references inside them
    Symbol(String)
}

You can read more about generics and traits in the official guide, though at the moment it lacks information on trait objects. Please do ask if you don't understand something.

Update

'a in

enum Expr<'a> {  // '
    Numeric(&'a Num + 'a),
    Symbol(String)
}

is a lifetime parameter. It defines both the lifetime of a reference and of trait object internals inside Numeric variant. &'a Num + 'a is a type that you can read as "a trait object behind a reference which lives at least as long as 'a with references inside it which also live at least as long as 'a". That is, first, you specify 'a as a reference lifetime: &'a, and second, you specify the lifetime of trait object internals: Num + 'a. The latter is needed because traits can be implemented for any types, including ones which contain references inside them, so you need to put the minimum lifetime of these references into trait object type too, otherwise borrow checking won't work correctly with trait objects.

With Box the situation is very similar. Box<Num + 'static> is "a trait object inside a heap-allocated box with references inside it which live at least as long as 'static". The Box type is a smart pointer for heap-allocated owned data. Because it owns the data it holds, it does not need a lifetime parameter like references do. However, the trait object still can contain references inside it, and that's why Num + 'a is still used; I just chose to use 'static lifetime instead of adding another lifetime parameter. This is because numerical types are usually simple and don't have references inside them, and it is equivalent to 'static bound. You are free to add a lifetime parameter if you want, of course.

Note that all of these variants are correct:

&'a SomeTrait + 'a
&'a SomeTrait + 'static
Box<SomeTrait + 'a>  // '
Box<SomeTrait + 'static>

Even this is correct, with 'a and 'b as different lifetime parameters:

&'a SomeTrait + 'b

though this is rarely useful, because 'b must be at least as long as 'a (otherwise internals of the trait object could be invalidated while it itself is still alive), so you can just as well use &'a SomeTrait + 'a.

like image 161
Vladimir Matveev Avatar answered Feb 18 '23 22:02

Vladimir Matveev