В чем разница между <T: Trait> Box <T> и & Trait/Box <Trait>?

При написании кода с признаками вы можете поместить признак в привязку к значению:

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);
}

Или непосредственно в Box или ссылочном типе:

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);
}

Они дают тот же результат. Так в чем же разница?

(Этот вопрос был вдохновлен другим вопросом, где это было всего лишь одной из нескольких взаимосвязанных концепций)

Ответ 1

С <T: Trait> Box<T> вы используете привязку, чтобы сообщить компилятору, что вы хотите Box с экземпляром какого-либо типа T, который реализует Trait, и вы укажете T, когда вы его используете, Компилятор Rust скорее всего создаст отличный, эффективный код для каждого другого T в вашем коде (мономорфизация).

С Box<Trait> вы сообщаете компилятору, что хотите Box с объектом-символом, указатель на неизвестный тип, который реализует Trait, что означает, что компилятор будет использовать динамическую отправку.

Я включил два примера, которые немного меняют разницу:

<T: Trait> Box<T>, т.е. граница привязки:

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>, т.е. объект-объект:

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"));
}

Подробнее о различиях между границами признаков и объектами признаков я рекомендую раздел о объектах признаков в первом издании книги Rust.

Ответ 2

Важно, чтобы не иметь, чтобы поставить общий тип за ссылкой (например, & или Box), вы можете принять его напрямую:

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

fn main() {
    myfunction3(5);
}

Это имеет те же преимущества мономорфизации, что и недостаток дополнительного распределения памяти (Box), или ему нужно сохранить значение где-то (&).

Я бы сказал, что generics часто должны быть выбором по умолчанию - вам нужен только объект-признак, когда есть динамическая отправка/неоднородность.