Rust中定义含高阶类型泛型的Trait时的生命周期问题
Rust中Trait泛型与生命周期的匹配错误解决方法
问题背景
想要定义一个Trait:Trait层级带有泛型参数BorrowedValue,方法层级带有生命周期参数'a,方法的实际参数类型为BorrowedValue<'a>。初始化结构体shows_i32_in_capitals时固定高阶类型BorrowedValue,但不固定生命周期'a,希望在调用show_debug时再确定该生命周期,但编译器报错。
原代码
// Constructs a borrowed value with a specific lifetime trait ConstructI32AsBorrowed<'a>: 'a { fn construct(x: &'a i32) -> Self; } // A struct which implements this #[derive(Debug)] struct BorrowedI32<'a> { value: &'a i32 } impl<'a> ConstructI32AsBorrowed<'a> for BorrowedI32<'a> { fn construct(value: &'a i32) -> Self { Self { value } } } // This is the important bit // A trait which represents BorrowedValue as a String, say in some special way // note that the type parameter BorrowedValue exists at the trait level, but the // lifetime 'a exists at the method level trait ShowBorrowedValue<BorrowedValue: std::fmt::Debug> { fn show_debug(&self, borrowed: BorrowedValue) -> String where BorrowedValue: for<'a> ConstructI32AsBorrowed<'a>; } // Define a simple struct which implements ShowBorrowedValue by capitalizing the debug outputs struct ShowsI32InCapitals; impl<BorrowedValue: std::fmt::Debug> ShowBorrowedValue<BorrowedValue> for ShowsI32InCapitals { fn show_debug(&self, borrowed: BorrowedValue) -> String where BorrowedValue: for<'a> ConstructI32AsBorrowed<'a> { format!("{:?}", borrowed).to_string().to_uppercase() } } pub fn main() { // We have a single instance of our struct let shows_i32_in_capitals = ShowsI32InCapitals; // But we want to apply it to two different borrowed values with two different lifetimes; // this checks that the `'a ` lifetime argument is not fixed at the level of the struct { let val_a = BorrowedI32::construct(&0_i32); shows_i32_in_capitals.show_debug(val_a); } { let val_b = BorrowedI32::construct(&1_i32); shows_i32_in_capitals.show_debug(val_b); } }
编译器错误信息
error: implementation of `ConstructI32AsBorrowed` is not general enough --> src/main.rs:43:9 | 43 | shows_i32_in_capitals.show_debug(val_a); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ implementation of `ConstructI32AsBorrowed` is not general enough | = note: `ConstructI32AsBorrowed<'0>` would have to be implemented for the type `BorrowedI32<'_>`, for any lifetime `'0`... = note: ...but `ConstructI32AsBorrowed<'1>` is actually implemented for the type `BorrowedI32<'1>`, for some specific lifetime `'1
错误原因
错误核心在于约束BorrowedValue: for<'a> ConstructI32AsBorrowed<'a>要求同一个具体类型(比如BorrowedI32<'static>)对任意生命周期'a都实现ConstructI32AsBorrowed<'a>,但实际上BorrowedI32<'a>是绑定到特定生命周期的类型,不同生命周期的BorrowedI32是完全不同的类型,每个BorrowedI32<'a>只对自身的生命周期'a实现了ConstructI32AsBorrowed<'a>,无法满足“任意生命周期”的约束。
解决方案
方案一:调整ShowBorrowedValue的泛型设计(简化版)
将ShowBorrowedValue的泛型参数移除,改为在方法层面约束参数类型,这样就不需要固定高阶类型,而是接受任意符合条件的带生命周期的类型:
// Constructs a borrowed value with a specific lifetime trait ConstructI32AsBorrowed<'a>: 'a { fn construct(x: &'a i32) -> Self; } // A struct which implements this #[derive(Debug)] struct BorrowedI32<'a> { value: &'a i32 } impl<'a> ConstructI32AsBorrowed<'a> for BorrowedI32<'a> { fn construct(value: &'a i32) -> Self { Self { value } } } // 调整Trait设计:移除泛型参数,在方法层面约束参数 trait ShowBorrowedValue { fn show_debug<'a, BorrowedValue>(&self, borrowed: BorrowedValue) -> String where BorrowedValue: std::fmt::Debug + ConstructI32AsBorrowed<'a>; } // 实现Trait时无需泛型参数 struct ShowsI32InCapitals; impl ShowBorrowedValue for ShowsI32InCapitals { fn show_debug<'a, BorrowedValue>(&self, borrowed: BorrowedValue) -> String where BorrowedValue: std::fmt::Debug + ConstructI32AsBorrowed<'a>, { format!("{:?}", borrowed).to_uppercase() } } pub fn main() { let shows_i32_in_capitals = ShowsI32InCapitals; { let val_a = BorrowedI32::construct(&0_i32); shows_i32_in_capitals.show_debug(val_a); } { let val_b = BorrowedI32::construct(&1_i32); shows_i32_in_capitals.show_debug(val_b); } }
方案二:通过类型构造器固定高阶类型(贴合原始意图)
如果需要严格固定高阶类型(即shows_i32_in_capitals仅针对BorrowedI32这类构造器),可以定义一个封装类型构造器的Trait,以此约束ShowBorrowedValue的泛型参数:
// 定义类型构造器Trait:表示可以构造任意生命周期的借用值 trait BorrowedValueConstructor { type Value<'a> where Self: 'a; fn construct<'a>(x: &'a i32) -> Self::Value<'a>; } // 让BorrowedI32实现构造器Trait #[derive(Debug)] struct BorrowedI32<'a> { value: &'a i32 } impl BorrowedValueConstructor for BorrowedI32<'_> { type Value<'a> = BorrowedI32<'a>; fn construct<'a>(x: &'a i32) -> Self::Value<'a> { BorrowedI32 { value: x } } } // 调整ShowBorrowedValue,泛型参数为构造器Trait trait ShowBorrowedValue<C: BorrowedValueConstructor> { fn show_debug<'a>(&self, borrowed: C::Value<'a>) -> String where C::Value<'a>: std::fmt::Debug; } // 实现Trait,约束构造器的输出类型可Debug struct ShowsI32InCapitals; impl<C: BorrowedValueConstructor> ShowBorrowedValue<C> for ShowsI32InCapitals where for<'a> C::Value<'a>: std::fmt::Debug, { fn show_debug<'a>(&self, borrowed: C::Value<'a>) -> String { format!("{:?}", borrowed).to_uppercase() } } pub fn main() { let shows_i32_in_capitals = ShowsI32InCapitals; { let val_a = BorrowedI32::construct(&0_i32); // 编译器自动推断出C为BorrowedI32<'_> shows_i32_in_capitals.show_debug(val_a); } { let val_b = BorrowedI32::construct(&1_i32); shows_i32_in_capitals.show_debug(val_b); } }
内容的提问来源于stack exchange,提问作者Chris J Harris
相关产品推荐
相关产品推荐

