Реализация метода признака, возвращающего ограниченную ссылку на время жизни для принадлежащего типа

Предположим, у меня есть эта структура и эта черта:

#[derive(Debug)]
pub struct New<T>(T);

pub trait AsRefNew<'a> {
    fn as_ref(&self) -> New<&'a str>;
}

То есть черта AsRefNew позволяет возвращать ссылку с заданным временем жизни 'a, заключенную в New newtype. Это время жизни 'a может отличаться (и будет) от времени жизни параметра &self.

Теперь я могу реализовать эту черту для New(&str) и сделать так, чтобы время жизни вывода было временем жизни обернутого &str:

impl<'a> AsRefNew<'a> for New<&'a str> {
    fn as_ref(&self) -> New<&'a str>{
        New(self.0)
    }
}

Моя проблема в том, что я хотел бы реализовать черту для New(String), и на этот раз я хотел бы, чтобы 'a действительно соответствовал времени жизни self. Насколько я понимаю, должно работать что-то подобное:

impl<'a> AsRefNew<'a> for New<String> where Self: 'a{
    fn as_ref(&self) -> New<&'a str> {
        New(self.0.as_str())
    }
}

За исключением того, что это не так:

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
  --> src/main.rs:16:20
   |
16 |         New(self.0.as_str())
   |                    ^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 15:5...
  --> src/main.rs:15:5
   |
15 |     fn as_ref(&self) -> New<&'a str> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...so that reference does not outlive borrowed content
  --> src/main.rs:16:13
   |
16 |         New(self.0.as_str())
   |             ^^^^^^
note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 14:6...
  --> src/main.rs:14:6
   |
14 | impl<'a> AsRefNew<'a> for New<String> where Self: 'a{
   |      ^^
note: ...so that the expression is assignable
  --> src/main.rs:16:9
   |
16 |         New(self.0.as_str())
   |         ^^^^^^^^^^^^^^^^^^^^
   = note: expected `New<&'a str>`
              found `New<&str>`

Я пробовал разные варианты продолжительности жизни и общие, но не могу найти лучшего способа выразить тот факт, что в данном случае я хочу, чтобы 'a совпадал с '_.

Цель состоит в том, чтобы этот фрагмент кода работал:

fn main() {
    // This works:
    let a = String::from("Hey");
    let b;
    {
        let c = New(a.as_str());
        b = c.as_ref().0;
    }
    println!("{:?}", b);
    
    // I would like that to work as well:
    let a = String::from("Ho");
    let b;
    let c = New(a);
    {
        b = c.as_ref().0;
    }
    println!("{:?}", b);
}

Любые идеи ?


person Alex Péré    schedule 28.01.2021    source источник
comment
Я не уверен, можно ли ответить на этот вопрос, пока вы не подробно остановитесь на этом. Это время жизни 'a может отличаться (и будет) от времени жизни параметра &self с некоторыми конкретными примерами. Измените свой вопрос, добавив в него дополнительных уточняющих деталей. Учитывая ваш вопрос как есть, я смог придумать этот пример., но я не уверен, действительно ли это решит вашу проблему.   -  person pretzelhammer    schedule 28.01.2021
comment
Спасибо, что посмотрели! Я добавил пример, чтобы показать, что я хотел бы иметь! Так что мне действительно нужна была реализация для New<String>, а не для New<&String>.   -  person Alex Péré    schedule 28.01.2021


Ответы (2)


Как объяснил Свен, для того, чтобы это работало, нам понадобятся два разных прототипа метода, а это невозможно с чертой AsRefNew, как она определена.

Тем не менее, его можно изменить, чтобы небольшой фрагмент работал, например вводим в подпись второе время жизни:

#[derive(Debug)]
pub struct New<T>(T);

pub trait AsRefNew<'b, 'a> {
    fn as_ref(&'b self) -> New<&'a str>;
}

impl<'a> AsRefNew<'_, 'a> for New<&'a str> {
    fn as_ref(&self) -> New<&'a str>{
        New(self.0)
    }
}

impl<'b, 'a> AsRefNew<'b, 'a> for New<String> where 'b:'a {
    fn as_ref(&'b self) -> New<&'a str> {
        New(self.0.as_str())
    }
}

impl<T> New<T> {
    pub fn test<'b, 'a>(&'b self) -> New<&'a str> where Self: AsRefNew<'b, 'a> {
        self.as_ref()
    }
}

Следующий фрагмент теперь работает:

fn main() {
    // This works:
    let a = String::from("Hey");
    let b;
    {
        let c = New(a.as_str());
        b = c.as_ref().0;
    }
    println!("{:?}", b);
    
    // It now works
    let a = String::from("Ho");
    let b;
    let c = New(a);
    {
        b = c.as_ref().0;
    }
    println!("{:?}", b);
}

То же самое и с этой общей реализацией метода для типа New:

impl<T> New<T> {
    pub fn test<'b, 'a>(&'b self) -> New<&'a str> where Self: AsRefNew<'b, 'a> {
        self.as_ref()
    }
}

Единственная проблема сейчас в том, что подпись супер некрасивая! Интересно, можно ли сделать это проще с помощью gats.

person Alex Péré    schedule 28.01.2021

На самом деле это невозможно. У методов-признаков может быть только один прототип, и все реализации должны соответствовать этому прототипу.

В случае New<&'a str> ваш прототип должен быть тем, что у вас есть

fn as_ref(&self) -> New<&'a str>;

С другой стороны, для случая New<String> вам потребуется

fn as_ref<'b>(&'b self) -> New<&'b str>;

вместо этого, т.е. время жизни возвращаемого значения должно быть привязано к времени жизни ссылки self. Этот прототип отличается, поскольку время жизни является частью прототипа.

Ваша попытка решить эту проблему с Self: 'a признаком не сработает. Тип Self здесь New<String>, который не содержит никаких ссылок, поэтому на самом деле он тривиально выполняет Self: 'static и, следовательно, любое ограничение времени жизни. Граница никак не ограничивает тип Self. Вы не можете ограничить время жизни ссылки self на уровне impl, и ограничение его на уровне определения функции приведет к отклонению от прототипа в определении признака, как объяснено выше.

По той же причине вы не можете получить &'a str от Cow<'a, str> путем разыменования. Cow может принадлежать, и в этом случае возвращенная ссылка может существовать только до тех пор, пока self ссылка используется для разыменования.

person Sven Marnach    schedule 28.01.2021
comment
Это так грустно ! Да, я думал, что добавление привязки может каким-то образом намекнуть компилятору на понимание того, что &self на самом деле &'a self, но это определенно не работает. Есть ли другие идеи о том, как заставить мой сниппет работать? Другого решения найти не смог, но может быть! - person Alex Péré; 28.01.2021
comment
На самом деле нет смысла заставлять это работать. В том месте, где вы используете трейт, например в универсальном коде с T: AsRefNew<'a> компилятор должен иметь возможность определять время жизни ссылки, возвращаемой as_ref(), без знания фактического типа T, поскольку в противном случае было бы невозможно заимствовать-проверить общий код. - person Sven Marnach; 28.01.2021
comment
Да, именно этого я и хотел добиться с помощью этой черты. Но благодаря вашему замечанию теперь я понимаю, что я хотел, чтобы прототип метода каким-то образом изменялся между реализациями, но это действительно невозможно. - person Alex Péré; 28.01.2021
comment
Только что нашел способ заставить его работать, изменив подпись, но теперь это ужасно некрасиво. Спасибо за отзыв;) - person Alex Péré; 28.01.2021