Так что у типажных объектов не может быть методов с дженериками - это выглядит нормально. Но на этом языке единственные способы использования механизма абстракции доступны через универсальные типы и типажные объекты. Это означает, что для каждой черты я должен заранее решить, можно ли ее вообще использовать как объект, и везде использовать dyn вместо impl. И все взятые внутри него черты должны быть одинаковыми, чтобы поддерживать это. Это очень уродливо. Можете ли вы что-нибудь предложить или рассказать, почему это так?

fn main() {}

// some abstracted thing
trait Required {
    fn f(&mut self, simple: i32);
}

// this trait doesn't know that it's going to be used by DynTrait
// it just takes Required as an argument
// nothing special
trait UsedByDyn {
    // this generic method doesn't allow this trait to be dyn itself
    // no dyn here: we don't know about DynTrait in this scope
    fn f(&mut self, another: impl Required);
}

// this trait needs to use UsedByDyn as a function argument
trait DynTrait {
    // since UsedByDyn uses generic methods it can't be dyn itself
    // the trait `UsedByDyn` cannot be made into an object
    //fn f(&mut self, used: Box<dyn UsedByDyn>);

    // we can't use UsedByDyn without dyn either otherwise Holder can't use us as dyn
    // the trait `DynTrait` cannot be made into an object
    // fn f(&mut self, used: impl UsedByDyn);

    // how to use UsedByDyn here?
}

struct Holder {
    CanBeDyn: Box<dyn DynTrait>,
}
0
Vlad 21 Ноя 2020 в 14:56

2 ответа

Лучший ответ

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

struct Holder {
    dyn_traits: Vec<Box<dyn DynTrait>>,
}

// this trait doesn't know that it's going to be used by DynTrait
// it just takes ObjectFriendly as an argument
// nothing special
trait ObjectUnfriendly {
    // this generic method doesn't allow this trait to be dyn itself
    // no dyn here: we don't know about DynTrait in this scope
    fn f(&mut self, another: &impl ObjectFriendly);
    fn f2(&mut self, another: &mut impl ObjectFriendly);
    fn f3(&mut self, another: impl ObjectFriendly);
}

trait ObjectFriendly {
    fn f(&mut self, simple: i32);
    fn f2(&self, simple: i32);
}

// this trait needs to use the trait above as a function argument
trait DynTrait {
    // since that trait uses generic methods it can't be dyn itself
    // the trait cannot be made into an object
    //fn f(&mut self, used: Box<dyn ObjectUnfriendly>);

    // we can't use that trait without dyn either otherwise Holder can't use us as dyn
    // the trait `DynTrait` cannot be made into an object
    // fn f(&mut self, used: impl ObjectUnfriendly);

    // how to use ObjectUnfriendly here?
    // we use our own extension trait that is object-friendly
    fn f(&mut self, used: dyn NowObjectFriendly);
}

// our own object-friendly version
trait NowObjectFriendly {
    // if arguments are ObjectFriendly - we are lucky
    fn f(&mut self, another: &dyn ObjectFriendly);
    fn f2(&mut self, another: &mut dyn ObjectFriendly);
    fn f3(&mut self, another: Box<dyn ObjectFriendly>);

    // if not - we can just accept the specific struct we need
    // fn f3(&mut self, another: SomeImpl);

    // or do the same thing by making an extension trait
    // fn f3(&mut self, another: Box<dyn ObjectFriendly2Ex>);
}

// delegate implementation
impl<T: ObjectUnfriendly> NowObjectFriendly for T {
    fn f(&mut self, another: &dyn ObjectFriendly) {
        self.f(&ObjectFriendly2AsImpl(another));
    }

    fn f2(&mut self, another: &mut dyn ObjectFriendly) {
        self.f2(&mut ObjectFriendly2AsImpl(another));
    }

    fn f3(&mut self, another: Box<dyn ObjectFriendly>) {
        self.f3(ObjectFriendly2AsImpl(another));
    }

    // if not object friendly - we can just accept the specific struct we need
    // fn f3(&mut self, another: SomeImpl) {
    //     SomeImpl::f3(self, another);
    // }

    // or do the same thing for that trait by making another extension trait
    // fn f3(&mut self, another: Box<dyn ObjectFriendly2Ex>) {
    //     self.f3(another);
    // }
}

// for this delegation to work
// we need to make it convertible to impl

// can't implement foreign traits on foreign types
struct ObjectFriendly2AsImpl<T>(T);

impl ObjectFriendly for ObjectFriendly2AsImpl<&dyn ObjectFriendly> {
    fn f(&mut self, simple: i32) {
        unreachable!()
    }

    fn f2(&self, simple: i32) {
        (*self.0).f2(simple)
    }
}

impl ObjectFriendly for ObjectFriendly2AsImpl<&mut dyn ObjectFriendly> {
    fn f(&mut self, simple: i32) {
        (*self.0).f(simple)
    }

    fn f2(&self, simple: i32) {
        (*self.0).f2(simple)
    }
}

impl ObjectFriendly for ObjectFriendly2AsImpl<Box<dyn ObjectFriendly>> {
    fn f(&mut self, simple: i32) {
        (*self.0).f(simple)
    }

    fn f2(&self, simple: i32) {
        (*self.0).f2(simple)
    }
}

Если есть макрос для этой или более легкой реализации, прокомментируйте.

0
Vlad 22 Ноя 2020 в 13:37

Это означает, что для каждой черты я должен заранее решить, можно ли ее вообще использовать как объект, и везде использовать dyn вместо impl.

Вы можете это сделать, но, к счастью, это не единственный вариант.

Вы также можете записывать свои черты, как обычно, используя, где это необходимо, обобщения. Если / когда вам нужны объекты-признаки, определите новый объектно-безопасный признак, который вы используете локально, и который предоставляет подмножество API, которое вам действительно нужно в этом месте.

Например, предположим, что у вас есть или вы используете не объектно-безопасную черту:

trait Serialize {
    /// Serialize self to the given IO sink
    fn serialize(&self, sink: &mut impl io::Write);
}

Эту черту нельзя использовать как объект черты, потому что она (предположительно для обеспечения максимальной эффективности) имеет общий метод. Но это не должно мешать вашему коду использовать объекты признаков для доступа к функциям признака. Допустим, вам нужно поместить значения Serialize в рамку, чтобы удерживать их в векторе, который вы сохраните в файл в массовом порядке :

// won't compile
struct Pool {
    objs: Vec<Box<dyn Serialize>>,
}

impl Pool {
    fn add(&mut self, obj: impl Serialize + 'static) {
        self.objs.push(Box::new(obj) as Box<dyn Serialize>);
    }

    fn save(&self, file: &Path) -> io::Result<()> {
        let mut file = io::BufWriter::new(std::fs::File::create(file)?);
        for obj in self.objs.iter() {
            obj.serialize(&mut file);
        }
        Ok(())
    }
}

Вышеупомянутое не компилируется, потому что Serialize не является объектно-безопасным. Но - вы можете легко определить новую объектно-безопасную характеристику, которая удовлетворяет требованиям Pool:

// object-safe trait, Pool's implementation detail
trait SerializeFile {
    fn serialize(&self, sink: &mut io::BufWriter<std::fs::File>);
}

// Implement `SerializeFile` for any T that implements Serialize
impl<T> SerializeFile for T
where
    T: Serialize,
{
    fn serialize(&self, sink: &mut io::BufWriter<std::fs::File>) {
        // here we can access `Serialize` because `T` is a concrete type
        Serialize::serialize(self, sink);
    }
}

Теперь Pool просто работает, используя dyn SerializeFile (детская площадка):

struct Pool {
    objs: Vec<Box<dyn SerializeFile>>,
}

impl Pool {
    fn add(&mut self, obj: impl Serialize + 'static) {
        self.objs.push(Box::new(obj) as Box<dyn SerializeFile>);
    }

    // save() defined the same as before
    ...
}

Определение отдельной объектно-безопасной характеристики может показаться ненужной работой - если исходная характеристика достаточно проста, вы, безусловно, можете сделать ее объектно-безопасной для начала. Но некоторые черты либо слишком общие, либо слишком ориентированы на производительность, чтобы сделать их объектно-безопасными с самого начала, и в этом случае хорошо помнить, что можно оставить их общими. Когда вам действительно нужна объектно-безопасная версия, это обычно будет для конкретной задачи, где настраиваемая объектно-безопасная черта, реализованная в терминах исходной черты, будет выполнять свою работу.

3
user4815162342 22 Ноя 2020 в 10:16