Общий, который может быть объектом типа или ссылкой типа

Учитывая структуру и черту:

// Minimal version of the actual data structure and trait
trait MyTrait {
    fn blub(&mut self);
}
struct MyStruct;
impl MyTrait for MyStruct {
    fn blub(&mut self) {
        println!("Blub!");
    }
}

Я хотел бы создать структуру, которая может содержать объект, реализующий MyTrait:

impl<T> Foo<T>
where
    T: MyTrait,
{
    fn new(t: T) -> Self {
        Self { t }
    }

    fn run(&mut self) {
        // Execute `blub` of `t`.
        // Something like:
        self.t.blub();
    }
}

Пока это легко. Теперь самое главное: я хочу принимать как собственные, так и изменяемые типы, например:

fn main() {
    let t0 = MyStruct;
    let mut f0 = Foo::new(t0);
    f0.run();

    let mut t1 = MyStruct;
    let mut f1 = Foo::new(&mut t1);
    f1.run();
}

Код здесь, конечно, не работает, потому что &mut MyStruct не реализует MyTrait.

Теоретически это должно быть возможно, потому что MyTrait::blub принимает &mut self, что совместимо как с собственными, так и с изменяемо заимствованными типами.


Вот как далеко я зашел. Работает, но есть две проблемы:

  • У него бессмысленный второй общий
  • Требуется PhantomData
use std::{borrow::BorrowMut, marker::PhantomData};

// Minimal version of the actual data structure and trait
trait MyTrait {
    fn blub(&mut self);
}
struct MyStruct;
impl MyTrait for MyStruct {
    fn blub(&mut self) {
        println!("Blub!");
    }
}

// Object that shall carry objects OR mutable references of type `MyTrait`
struct Foo<T, U> {
    t: T,
    _p: PhantomData<U>,
}

impl<T, U> Foo<T, U>
where
    T: BorrowMut<U>,
    U: MyTrait,
{
    fn new(t: T) -> Self {
        Self { t, _p: PhantomData }
    }

    fn run(&mut self) {
        self.t.borrow_mut().blub();
    }
}

fn main() {
    let t0 = MyStruct;
    let mut f0 = Foo::new(t0);
    f0.run();

    let mut t1 = MyStruct;
    let mut f1: Foo<_, MyStruct> = Foo::new(&mut t1);
    f1.run();
}
Blub!
Blub!

Есть ли способ реализовать это более элегантно?


Единственный другой элегантный способ, который я видел до сих пор, — это impl MyTrait for &mut MyStruct. К сожалению, я не владею этой чертой или типом, поэтому я не могу этого сделать. Хотя, пожалуйста, скажите мне, если мои попытки здесь ошибочны, и все это проблема XY; и на самом деле я должен сообщить об этой проблеме в указанной библиотеке, чтобы они могли добавить это impl.

Вы можете сделать Foo::run общим поверх U: MyTrait и ограничить T: BorrowMut<U> (см. игровая площадка), однако для его вызова вызывающим сторонам может потребоваться явно указать U, чтобы устранить неоднозначность.

eggyal 10.01.2023 06:28

@eggyal Да, это работает, но у него несколько неудобный API.

Finomnis 10.01.2023 08:44
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
1
2
68
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Самый простой способ, наверное, добавить еще одну имплантацию MyTrait вместо &mut MyStruct.

impl MyTrait for &mut MyStruct {
    fn blub(&mut self) {
        println!("Blub!");
    }
}

Если у вас нет доступа к структуре или свойству, вы можете использовать enum для управления собственными и заимствованными версиями и реализовать Deref/DerefMut, чтобы использование t оставалось прежним.

enum Container<'a, T> {
    Owned(T),
    Borrowed(&'a mut T)
}

impl<'a, T: MyTrait> From<T> for Container<'a, T> {
    fn from(t: T) -> Self {
        Self::Owned(t)
    }
}

impl<'a, T: MyTrait> From<&'a mut T> for Container<'a, T> {
    fn from(t: &'a mut T) -> Self {
        Self::Borrowed(t)
    }
}

impl<'a, T> Deref for Container<'a, T> {
    type Target = T;
    fn deref(&self) -> &Self::Target {
        match self {
            Self::Owned(o) => o,
            Self::Borrowed(o) => o
        }
    }
}

impl<'a, T> DerefMut for Container<'a, T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        match self {
            Self::Owned(o) => o,
            Self::Borrowed(o) => o
        }
    }
}

struct Foo<'a, T> {
    t: Container<'a, T>
}

impl<'a, T> Foo<'a, T>
where
    T: MyTrait,
{
    fn new(t: impl Into<Container<'a, T>>) -> Self {
        Self{ t: t.into() }
    }

    fn run(&mut self) {
        self.t.blub();
    }
}

@Finomnis Я обновил свой ответ альтернативой.

pigeonhands 10.01.2023 01:55

Классная идея, перечисление :) может сработать.

Finomnis 10.01.2023 08:39

Почему это позволяет вам и impl<'a, T> From<T> for Container<'a, T>, и impl<'a, T> From<&'a mut T> for Container<'a, T> одновременно? Разве T в первом не может быть &mut? Или тот факт, что у первого T недостаточно времени жизни, чтобы компилятор понял, что они никогда не могут вызвать оба?

Finomnis 10.01.2023 08:42

@Finomnis Возможно, но если бы в первом было &mut T, то во втором было бы &mut &mut T.

pigeonhands 10.01.2023 08:55

На самом деле, без явных аннотаций типов: play.rust-lang.org/… Но интересно, возможно ли это вообще без явных аннотаций.

Finomnis 10.01.2023 08:57

@Finomnis, потому что вам нужны разные типы возврата в зависимости от T, переданного в new, имеет смысл, почему возвращаемое значение несколько неоднозначно для текущего преобразователя типов ржавчины, следовательно, аннотации типов. Я предполагаю, что вы, вероятно, могли бы заставить его работать без аннотаций типа с некоторой промежуточной чертой со связанным типом, но я не уверен, что это стоит сложности.

pigeonhands 10.01.2023 09:09

Я попробовал, основываясь на вашей идее и промежуточной сделке, но потом получаю ожидаемую ошибку «конфликтующие реализации»: play.rust-lang.org/…

Finomnis 10.01.2023 09:19

На самом деле, ваша первая идея действительно работает! Ему просто понадобились дополнительные аннотации черт в From, чтобы сделать его однозначным: play.rust-lang.org/…

Finomnis 10.01.2023 09:23

Другие решения, которые я нашел:

Это не требует просмотра перечисления во время выполнения, оно решает типы во время компиляции:

use std::marker::PhantomData;

// Minimal version of the actual data structure and trait
trait MyTrait {
    fn blub(&mut self);
}

struct MyStruct;
impl MyTrait for MyStruct {
    fn blub(&mut self) {
        println!("Blub!");
    }
}

trait MyTraitFrom<'a, T> {
    fn mytrait_from(value: &'a mut T) -> Self;
}

impl<'a, T: MyTrait> MyTraitFrom<'a, T> for &'a mut T {
    fn mytrait_from(value: &'a mut T) -> Self {
        value
    }
}

impl<'a, T: MyTrait> MyTraitFrom<'a, &'a mut T> for &'a mut T {
    fn mytrait_from(value: &'a mut &'a mut T) -> Self {
        let value: &'a mut T = value;
        value
    }
}

struct Foo<T, U> {
    t: T,
    _p: PhantomData<U>,
}

impl<'a, T: 'a, U: 'a> Foo<T, U>
where
    &'a mut U: MyTraitFrom<'a, T>,
    U: MyTrait,
{
    fn new(t: T) -> Self {
        Self { t, _p: PhantomData }
    }

    fn run(&'a mut self) {
        let u: &'a mut U = MyTraitFrom::mytrait_from(&mut self.t);
        u.blub();
    }
}

fn main() {
    let t0 = MyStruct;
    let mut f0 = Foo::new(t0);
    f0.run();

    let mut t1 = MyStruct;
    let mut f1 = Foo::new(&mut t1);
    f1.run();
}
Blub!
Blub!

Другие вопросы по теме