«Ожидаемая черта A, найдено и A» при попытке поместить объект черты - PullRequest
0 голосов
/ 16 сентября 2018

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

use std::borrow::Borrow;
use std::collections::HashMap;

trait A { 
    fn foobar(&self) {
        println!("!"); 
    } 
}

trait ProducerOrContainer {
    fn get_a<'a>(&'a self, name: &'a str) -> Option<Box<dyn A + 'a>>;
}

impl<'b, B: Borrow<A>> ProducerOrContainer for HashMap<&'b str, B> {
    fn get_a<'a>(&'a self, name: &'a str) -> Option<Box<dyn A + 'a>> {
        self.get(name).map(|borrow| Box::new(borrow.borrow()))
    }
}

Ошибка:

error[E0308]: mismatched types
  --> src/main.rs:20:9
   |
20 |         self.get(name).map(|borrow| Box::new(borrow.borrow()))
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected trait A, found &A
   |
   = note: expected type `std::option::Option<std::boxed::Box<dyn A + 'a>>`
              found type `std::option::Option<std::boxed::Box<&dyn A>>`

Что меня озадачивает, потому что я ожидал, что &A будет Aтоже.Я пытался impl<'a> A for &'a A, но это тоже не помогает.Есть ли способ это исправить?

Ответы [ 2 ]

0 голосов
/ 16 сентября 2018

Вы можете скомпилировать исходный код, реализовав A для &'_ dyn A и добавив явное приведение:

self.get(name).map(|borrow| Box::new(borrow.borrow()) as Box<dyn A>)

Закрытие не является сайтом принуждения . Компилятор просматривает содержимое замыкания, чтобы увидеть, что является возвращаемым значением, и приходит к выводу, что он возвращает Box<&'a dyn A>. Но само замыкание не может быть приведено от «функции, возвращающей Box<&'a dyn A>» к «функции, возвращающей Box<dyn A + 'a>», потому что эти типы структурно отличаются. Вы добавляете приведение, чтобы сообщить компилятору, что вы хотите, чтобы замыкание возвратило Box<dyn A>.

Но это немного глупо. Box указание ссылки здесь совершенно не нужно, а приведение к Box<dyn A> просто добавляет еще один уровень косвенности для вызывающей стороны. Было бы лучше вернуть тип, который инкапсулирует идею « или объект в штучной упаковке, или ссылка на объект черты», как ответ Питера Холла описывает.


В будущей версии Rust с родственными связанными типами («GAT») будет возможно сделать возвращаемый тип ассоциированным типом ProducerOrContainer, что-то вроде следующего:

trait ProducerOrContainer {
    type Result<'a>: A;
    fn get_a<'a>(&'a self, name: &'a str) -> Option<Result<'a>>;
}

С этим определением черты каждый тип, который реализует ProducerOrContainer, может выбирать, какой тип он возвращает, поэтому вы можете выбрать Box<dyn A> для некоторых impl s и &'a dyn A для других. Однако это невозможно в текущей версии Rust (1.29).

0 голосов
/ 16 сентября 2018

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

С этим требованием Box не будет работать. A Box владеет своими данными, но иногда вы заимствовали данные, которые нельзя переместить.

В стандартной библиотеке есть тип с именем Cow, который является абстракцией того, является ли значение заимствованным или принадлежащим. Тем не менее, он может быть не совсем подходящим для вас, потому что он не позволит вам владеть данными как Box, а также требует, чтобы ваш тип данных реализовывал ToOwned.

Но мы можем принять ваше требование и смоделировать его непосредственно как enum:

enum BoxOrBorrow<'a, T: 'a + ?Sized> {
    Boxed(Box<T>),
    Borrowed(&'a T),
}

И сделать его эргономичным, используя Deref:

use std::ops::Deref;

impl<'a, T> Deref for BoxOrBorrow<'a, T> {
    type Target = T;
    fn deref(&self) -> &T {
        match self {
            BoxOrBorrow::Boxed(b) => &b,
            BoxOrBorrow::Borrowed(b) => &b,
        }
    }
}

Это позволяет вам обрабатывать пользовательский тип BoxOrBorrow как любую другую ссылку - вы можете разыменовать его с помощью * или передать его любой функции, которая ожидает ссылку на T.

Вот как будет выглядеть ваш код:

trait ProducerOrContainer {
    fn get_a<'a>(&'a self, name: &'a str) -> Option<BoxOrBorrow<'a, dyn A + 'a>>;
}

impl<'b, B: Borrow<dyn A>> ProducerOrContainer for HashMap<&'b str, B> {
    fn get_a<'a>(&'a self, name: &'a str) -> Option<BoxOrBorrow<'a, dyn A + 'a>> {
        self.get(name)
            .map(|b| BoxOrBorrow::Borrowed(b.borrow()))
    }
}
...