Учитывая структуру и черту:
// 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
.
@eggyal Да, это работает, но у него несколько неудобный API.
Самый простой способ, наверное, добавить еще одну имплантацию 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 Я обновил свой ответ альтернативой.
Классная идея, перечисление :) может сработать.
Почему это позволяет вам и impl<'a, T> From<T> for Container<'a, T>
, и impl<'a, T> From<&'a mut T> for Container<'a, T>
одновременно? Разве T
в первом не может быть &mut
? Или тот факт, что у первого T
недостаточно времени жизни, чтобы компилятор понял, что они никогда не могут вызвать оба?
@Finomnis Возможно, но если бы в первом было &mut T
, то во втором было бы &mut &mut T
.
На самом деле, без явных аннотаций типов: play.rust-lang.org/… Но интересно, возможно ли это вообще без явных аннотаций.
@Finomnis, потому что вам нужны разные типы возврата в зависимости от T
, переданного в new
, имеет смысл, почему возвращаемое значение несколько неоднозначно для текущего преобразователя типов ржавчины, следовательно, аннотации типов. Я предполагаю, что вы, вероятно, могли бы заставить его работать без аннотаций типа с некоторой промежуточной чертой со связанным типом, но я не уверен, что это стоит сложности.
Я попробовал, основываясь на вашей идее и промежуточной сделке, но потом получаю ожидаемую ошибку «конфликтующие реализации»: play.rust-lang.org/…
На самом деле, ваша первая идея действительно работает! Ему просто понадобились дополнительные аннотации черт в From
, чтобы сделать его однозначным: play.rust-lang.org/…
Другие решения, которые я нашел:
Это не требует просмотра перечисления во время выполнения, оно решает типы во время компиляции:
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!
Вы можете сделать
Foo::run
общим поверхU: MyTrait
и ограничитьT: BorrowMut<U>
(см. игровая площадка), однако для его вызова вызывающим сторонам может потребоваться явно указатьU
, чтобы устранить неоднозначность.