В чем разница между ‹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);
}

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

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


person Erik Vesteraas    schedule 17.07.2017    source источник


Ответы (2)


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

С помощью Box<Trait> вы сообщаете компилятору, что вам нужен Box с типажным объектом, указателем на unknown тип, который реализует 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.

person Erik Vesteraas    schedule 17.07.2017

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

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

fn main() {
    myfunction3(5);
}

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

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

person Shepmaster    schedule 30.10.2017