What is the difference between <T: Trait> Box<T> and &Trait / Box<Trait>?
Asked Answered
S

2

13

When writing code with traits you can put the trait in a trait bound:

use std::fmt::Debug;

fn myfunction1<T: Debug>(v: Box<T>) {
    println!("{:?}", v);
}

fn myfunction2<T: Debug>(v: &T) {
    println!("{:?}", v);
}

fn main() {
    myfunction1(Box::new(5));
    myfunction2(&5);
}

Or directly in a Box or reference type:

use std::fmt::Debug;

fn myfunction3(v: Box<Debug>) {
    println!("{:?}", v);
}

fn myfunction4(v: &Debug) {
    println!("{:?}", v);
}

fn main() {
    myfunction3(Box::new(5));
    myfunction4(&5);
}

These give the same output. So what is the difference?

(This question was inspired by another question where this was just one of several intermingled concepts)

Saltigrade answered 17/7, 2017 at 19:2 Comment(0)
S
12

With <T: Trait> Box<T> you are using a trait bound to tell the compiler that you want a Box with an instance of some type T which implements Trait, and you will specify T when you use it. The Rust compiler will likely create different, efficient, code for each different T in your code (monomorphization).

With Box<Trait> you are telling the compiler that you want a Box with a trait object, a pointer to an unknown type which implements Trait, which means that the compiler will use dynamic dispatch.

I've included two examples which makes the difference a bit clearer:

<T: Trait> Box<T>, i.e. trait bound:

use std::fmt::Debug;

struct Wrapper<T> {
    contents: Option<Box<T>>,
}

impl<T: Debug> Wrapper<T> {
    fn new() -> Wrapper<T> {
        Wrapper { contents: None }
    }

    fn insert(&mut self, val: Box<T>) {
    }
}

fn main() {
    let mut w = Wrapper::new();

    // makes T for w be an integer type, e.g. Box<i64>
    w.insert(Box::new(5));

    // type error, &str is not an integer type
    // w.insert(Box::new("hello"));
}

Box<Trait>, i.e. trait object:

use std::fmt::Debug;

struct Wrapper {
    contents: Option<Box<Debug>>,
}

impl Wrapper {
    fn new() -> Wrapper {
        Wrapper { contents: None }
    }

    fn insert(&mut self, val: Box<Debug>) {
    }
}

fn main() {
    let mut w = Wrapper::new();
    w.insert(Box::new(5));
    w.insert(Box::new("hello"));
}

For further details on the difference between trait bounds and trait objects I recommend the section on trait objects in the first edition of the Rust book.

Saltigrade answered 17/7, 2017 at 19:2 Comment(0)
P
5

Importantly, you don't have to put the generic type behind a reference (like & or Box), you can accept it directly:

fn myfunction3<T: Debug>(v: T) {
    println!("{:?}", v);
}

fn main() {
    myfunction3(5);
}

This has the same benefits of monomorphization without the downside of additional memory allocation (Box) or needing to keep ownership of the value somewhere (&).

I would say that generics should often be the default choice — you only require a trait object when there is dynamic dispatch / heterogeneity.

Platinumblond answered 30/10, 2017 at 12:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.