Упрощенный код:
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)
}
}
@Riwen Я могу изменить подпись Bar::bar
, чтобы обеспечить соблюдение этого правила.
Вы, вероятно, столкнулись с этим, потому что Self
является своего рода общим.
Проблема в том, что bar
может быть fn bar<'a>(a: &'a A) -> impl Foo { a }
, если for<'a> &'a A: Foo
.
Звучит как ошибка компилятора, выдающая неправильное сообщение об ошибке. Но я не думаю, что это все равно должно скомпилироваться — откуда вы знаете, что bar
и baz
возвращают одно и то же impl Foo
?
@BallpointBen, потому что baz
определяется именно так. impl Foo
в позиции возврата означает, что реализация функции может свободно выбирать любой тип возвращаемого значения, если этот тип возврата реализует Foo
, поэтому baz
может свободно выбрать возврат того же типа, что и bar
, если захочет.
@Jmb в вашей реализации по умолчанию — да, но разработчики могут переопределить эту реализацию по умолчанию на ту, которая возвращает другой impl Foo
. Если я не ошибаюсь, использование -> impl Foo
в типажах создает скрытый связанный тип, который определяется для каждого метода для каждого реализатора, поэтому нет ничего, что ограничивало бы bar
и baz
возвращать один и тот же тип. Вот почему работает ответ true-equals-false: с помощью одного связанного типа вы ограничиваете типы возвращаемых значений одинаковыми. Опять же, я думаю, что компилятор прав, что это не должно компилироваться, но ошибается в том, почему.
@BallpointBen да, разработчики могут выбрать другой impl Foo
, и что с того? Дело в том, что им разрешено выбирать одно и то же impl Foo
для обоих. Обратите внимание, что вы получаете ту же ошибку при реализации признака для данного struct
, хотя больше нет никакой возможности, что кто-то другой реализует то же самое trait
для того же struct
, используя разные типы (игровая площадка)
Я не до конца понимаю, почему это не работает, но обходной путь — сделать тип возвращаемого значения ассоциированным типом с признаком:
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
Я, конечно, заметил этот ответ. Я хочу подождать еще немного, прежде чем принять его, чтобы посмотреть, есть ли лучшее решение. Я просто говорю, что ваш ответ - это комментарий, а не ответ.
@ouuan Ответы, предполагающие более глубокое понимание проблемы, ценны, даже если они не дают конкретного ответа и не требуют комментариев.
«Возвращаемое значение baz не заимствует из локальной переменной. Теперь Rust думает, что да. Я хочу, чтобы Rust знал, что это не так». - но могло бы, нет? Ничто не мешает тому, что реализует
Foo
, фактически иметь ссылку наa