Rust думает, что аргумент заимствован в возвращаемом значении `impl Trait`, и жалуется, что "заимствованное значение не живет достаточно долго"

Упрощенный код:

struct A(/**/);

trait Foo {}

trait Bar {
    fn bar(a: &A) -> impl Foo;

    fn baz() -> impl Foo {
        let a = A();
        Self::bar(&a)
    }
}

Ошибка:

error[E0597]: `a` does not live long enough
  --> src/lib.rs:10:19
   |
9  |         let a = A();
   |             - binding `a` declared here
10 |         Self::bar(&a)
   |         ----------^^-
   |         |         |
   |         |         borrowed value does not live long enough
   |         argument requires that `a` is borrowed for `'static`
11 |     }
   |     - `a` dropped here while still borrowed

Эту проблему можно решить, вернув конкретный тип, реализующий Foo или Box<dyn Foo> вместо impl Foo, но я хочу продолжать возвращать impl Foo без дополнительных накладных расходов.

Примеры, которые работают, но мне не нужны:

struct A(/**/);

trait Foo {}

struct B;
impl Foo for B {}

trait Bar {
    fn bar(a: &A) -> B;

    fn baz() -> impl Foo {
        let a = A();
        Self::bar(&a)
    }
}
struct A(/**/);

trait Foo {}

trait Bar {
    fn bar(a: &A) -> Box<dyn Foo>;

    fn baz() -> Box<dyn Foo> {
        let a = A();
        Self::bar(&a)
    }
}

«Возвращаемое значение baz не заимствует из локальной переменной. Теперь Rust думает, что да. Я хочу, чтобы Rust знал, что это не так». - но могло бы, нет? Ничто не мешает тому, что реализует Foo, фактически иметь ссылку на a

Riwen 24.08.2024 10:59

@Riwen Я могу изменить подпись Bar::bar, чтобы обеспечить соблюдение этого правила.

ouuan 24.08.2024 11:01

Вы, вероятно, столкнулись с этим, потому что Self является своего рода общим.

cafce25 24.08.2024 11:04

Проблема в том, что bar может быть fn bar<'a>(a: &'a A) -> impl Foo { a }, если for<'a> &'a A: Foo.

jthulhu 24.08.2024 15:06

Звучит как ошибка компилятора, выдающая неправильное сообщение об ошибке. Но я не думаю, что это все равно должно скомпилироваться — откуда вы знаете, что bar и baz возвращают одно и то же impl Foo?

BallpointBen 24.08.2024 16:13

@BallpointBen, потому что baz определяется именно так. impl Foo в позиции возврата означает, что реализация функции может свободно выбирать любой тип возвращаемого значения, если этот тип возврата реализует Foo, поэтому baz может свободно выбрать возврат того же типа, что и bar, если захочет.

Jmb 24.08.2024 16:53

@Jmb в вашей реализации по умолчанию — да, но разработчики могут переопределить эту реализацию по умолчанию на ту, которая возвращает другой impl Foo. Если я не ошибаюсь, использование -> impl Foo в типажах создает скрытый связанный тип, который определяется для каждого метода для каждого реализатора, поэтому нет ничего, что ограничивало бы bar и baz возвращать один и тот же тип. Вот почему работает ответ true-equals-false: с помощью одного связанного типа вы ограничиваете типы возвращаемых значений одинаковыми. Опять же, я думаю, что компилятор прав, что это не должно компилироваться, но ошибается в том, почему.

BallpointBen 24.08.2024 22:24

@BallpointBen да, разработчики могут выбрать другой impl Foo, и что с того? Дело в том, что им разрешено выбирать одно и то же impl Foo для обоих. Обратите внимание, что вы получаете ту же ошибку при реализации признака для данного struct, хотя больше нет никакой возможности, что кто-то другой реализует то же самое trait для того же struct, используя разные типы (игровая площадка)

Jmb 24.08.2024 23:31
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
1
8
100
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Я не до конца понимаю, почему это не работает, но обходной путь — сделать тип возвращаемого значения ассоциированным типом с признаком:

struct A(/**/);

trait Foo {}

trait Bar {
    type BarRet: Foo;
    fn bar(a: &A) -> Self::BarRet;

    fn baz() -> impl Foo {
        let a = A();
        Self::bar(&a)
    }
}

детская площадка

Согласно rfc 3498

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

код может быть лишен сахара следующим образом:

trait Bar {
    fn bar<'a>(a: &'a A) -> impl Foo + 'a;

    fn baz() -> impl Foo {
        let a = A();

        Self::bar(&a)
    }
}

baz ожидает возврата impl Foo, в то время как bar возвращает impl Foo + 'a, поэтому компилятор жалуется borrowed value does not live long enough

Код в этого ответа работает, потому что возвращаемый тип Self::BarRet из bar не имеет общих параметров времени жизни, что означает, что возвращаемое значение bar не имеет ничего общего со временем жизни его параметра &A

Да, хорошее объяснение, но вы не дали решения проблемы.

ouuan 24.08.2024 18:33

Я думаю этот ответ станет хорошим вдохновением для решения проблемы @ouuan

caelansar 25.08.2024 05:33

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

ouuan 25.08.2024 07:46

@ouuan Ответы, предполагающие более глубокое понимание проблемы, ценны, даже если они не дают конкретного ответа и не требуют комментариев.

Chayim Friedman 25.08.2024 10:49

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