特征的 impl 可以指定来自方法的输入参数的生命周期吗?

Wea*_*ter 0 traits lifetime rust lifetime-scoping

对于一个类型

pub struct Child<'a> {
    buf: &'a mut [u8],
}
Run Code Online (Sandbox Code Playgroud)

我可以定义一个特征并实现该类型的特征,但其生命周期绑定到调用函数的上下文(而不是本地循环上下文):

pub trait MakeMut<'a> {
    fn make_mut(buf: &'a mut [u8]) -> Self;
}

impl<'a> MakeMut<'a> for Child<'a> {
    fn make_mut(buf: &'a mut [u8]) -> Self {
        Self { buf }
    }
}
Run Code Online (Sandbox Code Playgroud)

首先展示一个有点有效的示例,因为x仅在循环上下文中借用,因为 Child::make_mut 是在函数中硬编码的map1

pub fn map1<F>(mut func: F)
    where
        F: FnMut(&mut Child),
{
    let mut vec = vec![0; 16];
    let x = &mut vec;
    for i in 0..2 {
        let offset = i * 8;
        let s =  &mut x[offset..];
        let mut w = Child::make_mut(s);
        func(&mut w);
    }
}
Run Code Online (Sandbox Code Playgroud)

但是在尝试制作map2(map1的通用版本)时,T绑定到MakeMut特征,但具有整个函数体的生命周期,这将无法编译,有充分的理由(T创建的T生命周期: MakeMut<'a> 具有 map2 的生命周期,而不是内部循环):

pub fn map2<'a, F, T>(mut func: F)   // lifetime `'a` defined here
    where
        T: MakeMut<'a>,
        F: FnMut(&mut T),
{
    let mut vec = vec![0; 16];
    let x = &mut vec;
    for i in 0..2 {
        let offset = i * 8;
        let s =  &mut x[offset..];
        let mut w = T::make_mut(s);  // error: argument requires that `*x` is borrowed for `'a`
        func(&mut w);
    }
}
Run Code Online (Sandbox Code Playgroud)

我想做类似这样的事情,但当然它也不能编译:

pub trait MakeMut {
    fn make_mut<'a>(buf: &'a mut [u8]) -> Self;
}

impl<'a> MakeMut for Child<'a> {
    fn make_mut(buf: &'a mut [u8]) -> Self {    // lifetime mismatch
        Self{ buf }
    }
}
Run Code Online (Sandbox Code Playgroud)

与编译器错误:

error[E0308]: method not compatible with trait
  --> src/main.rs:45:5
   |
45 |     fn make_mut(buf: &'a mut [u8]) -> Self {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ lifetime mismatch
   |
   = note: expected fn pointer `fn(&'a mut [u8]) -> Child<'_>`
              found fn pointer `fn(&'a mut [u8]) -> Child<'_>`
note: the lifetime `'a` as defined here...
  --> src/main.rs:45:5
   |
45 |     fn make_mut(buf: &'a mut [u8]) -> Self {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...does not necessarily outlive the lifetime `'a` as defined here
  --> src/main.rs:44:6
   |
44 | impl<'a> MakeMut for Child<'a> {
   |      ^^
Run Code Online (Sandbox Code Playgroud)

是否有一种语法允许 a 的特征,Child<'a>其中 a'a由方法的输入参数定义make_mut?因此,可以为返回实例的特征定义一个通用函数,但实例生命周期不是整个函数,而只是由内部块定义的较短生命周期?

我知道生命周期是返回类型的一部分,但似乎更高级别的特征界限(HRTB)可以解决这个问题,除非我还没有找到一种方法来指定适合特征和方法签名的生命周期。

这是一个游乐场链接https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=fb28d6da9d89fde645edeb1ca0ae5b21

cdh*_*wie 5

您的第一次尝试接近您想要的。以供参考:

pub trait MakeMut<'a> {
    fn make_mut(buf: &'a mut [u8]) -> Self;
}

impl<'a> MakeMut<'a> for Child<'a> {
    fn make_mut(buf: &'a mut [u8]) -> Self {
        Self { buf }
    }
}
Run Code Online (Sandbox Code Playgroud)

T第一个问题是中的界限map2

pub fn map2<'a, F, T>(mut func: F)
where
    T: MakeMut<'a>,
    F: FnMut(&mut T),
Run Code Online (Sandbox Code Playgroud)

这需要编译器推导出适用于整个函数的单个值。'a 由于生存期参数来自函数外部,因此生存期'a必然比函数调用长,这意味着任何具有生存期的东西都必须比'a函数生存期长。从T::make_mut()调用开始向后推导,编译器最终推断出这x意味着&'a mut Vec<_>必须vec比函数调用更长寿,但由于它是本地函数,因此不可能做到这一点。

这可以通过使用更高等级的特征界限来修复,指示必须在任何可能的生命周期内T实现,其表达如下:MakeMut<'a> 'a

pub fn map2<F, T>(mut func: F)
where
    T: for<'a> MakeMut<'a>,
    F: FnMut(&mut T),
Run Code Online (Sandbox Code Playgroud)

通过此更改,代码可以编译

然后您会发现您实际上无法调用,map2因为T=Child<'_>您会在不同的地方遇到相同的问题。调用者必须为in指定一个特定的生命周期,但这与 HRTB 不一致——你已经有了,但 HRTB 想要,这又带来了该实现中的生命周期问题。'aChild<'a>impl<'a> MakeMut<'a> for Child<'a>impl<'a, 'b> MakeMut<'b> for Child<'a>make_mut

MakeMut解决这个问题的一种方法是解耦from的实现Child,提供使用关联类型的“工厂类型”。这样,调用者就不必提供任何可能在以后引起麻烦的讨厌的生命周期参数。

pub trait MakeMut<'a> {
    type Item;
    fn make_mut(buf: &'a mut [u8]) -> Self::Item;
}

struct ChildFactory;

impl<'a> MakeMut<'a> for ChildFactory {
    type Item = Child<'a>;
    fn make_mut(buf: &'a mut [u8]) -> Child<'a> {
        Child { buf }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我们修改map2以了解关联类型:

pub fn map2<F, T>(mut func: F)
where
    T: for<'a> MakeMut<'a>,
    F: for<'a, 'b> FnMut(&'b mut <T as MakeMut<'a>>::Item),
Run Code Online (Sandbox Code Playgroud)

哇哦

现在,我们终于可以使用map2

map2::<_, ChildFactory>(|v| {});
Run Code Online (Sandbox Code Playgroud)

游乐场