Rust结构体构造中Texture2d与Sampler生命周期不匹配问题求助
我之前也碰到过类似的生命周期困扰,咱们先拆解下你遇到的问题:你想让DefaultResourcePack同时持有Texture2d的所有权和依赖它的Sampler引用,但编译器一直报错,核心原因有两个:
- 你定义的
Sampler<'a, Texture2d>把生命周期'a绑定到了facade的引用上,但构造函数里创建的texture是局部变量,它的生命周期远短于'a; - 即使你把
texture移动到结构体中,Sampler引用的还是构造函数里的那个局部texture实例,当局部变量被移动后,这个引用就失效了——Rust不会自动把引用指向结构体里的新位置。
先看你贴的代码和报错:
你的代码
pub struct DefaultResourcePack<'a> { regions: [TexRegion; SHEET_TEX_ID_COUNT], sheet: Sampler<'a, Texture2d>, simple_program: Program, texture: Texture2d, } impl<'a> DefaultResourcePack<'a> { fn new<F: Facade>(facade: &'a F) -> DefaultResourcePack<'a> { // construct regions let sheet_dim: f32 = 16.0; let slot_size: f32 = 1.0 / sheet_dim; let mut regions = [TexRegion::new(0.0, 0.0, 0.0, 0.0); SHEET_TEX_ID_COUNT]; for &id in &ALL_SHEET_TEX_ID { let slot = sheet_tex_ord(id) as f32; let ord = sheet_tex_ord(id); regions[ord] = TexRegion::uvwh( (slot % sheet_dim) * slot_size, (slot - (slot % sheet_dim)) * slot_size, slot_size, slot_size, ) } // construct the sheet let image = image::load_from_memory(DEF_TEX_SHEET_BYTES) .expect("texture sheet load error") .to_rgba(); let image_dim = image.dimensions(); let image = RawImage2d::from_raw_rgba_reversed(&image.into_raw(), image_dim); let texture: Texture2d = Texture2d::new(facade, image).expect("texture creation error"); let sheet: Sampler<'a, Texture2d> = Sampler::new(&texture).magnify_filter(MagnifySamplerFilter::Nearest); // construct the program let simple_program = Program::new( facade, ProgramCreationInput::SourceCode { vertex_shader: SIMPLE_SHADER_V, tessellation_control_shader: None, tessellation_evaluation_shader: None, geometry_shader: None, fragment_shader: SIMPLE_SHADER_F, transform_feedback_varyings: None, outputs_srgb: false, uses_point_size: false, }, ).expect("GLSL compilation error"); // construct the pack DefaultResourcePack { regions, sheet, simple_program, texture, } } }
编译器报错
error[E0597]:
texture生命周期不足
--> src\graphics\resourcepack.rs:164:63
|
164 | let sheet: Sampler<'a, Texture2d> = Sampler::new(&texture)
| ^^^^^^^ 被借用的值生命周期不足
...
182 | }
| - 被借用的值仅存活至此处
| note: 被借用的值必须在impl块138:5处定义的生命周期'a内保持有效...
--> src\graphics\resourcepack.rs:138:5
|
138 | / impl<'a> DefaultResourcePack<'a> {
139 | | fn new<F: Facade>(facade: &'a F) -> DefaultResourcePack<'a> {
140 | | // construct regions
141 | | let sheet_dim: f32 = 16.0;
...
182 | | }
183 | | }
| |_____^
解决方案
方案1:用Rc共享所有权(最常用)
这个方法不需要修改太多代码,核心是让Texture2d的所有权被结构体和Sampler共享,这样就不存在引用失效的问题了:
use std::rc::Rc; // 移除结构体的生命周期参数,因为不需要绑定外部引用了 pub struct DefaultResourcePack { regions: [TexRegion; SHEET_TEX_ID_COUNT], // Sampler持有Rc<Texture2d>的引用(或者直接持有Rc,看Sampler的定义) sheet: Sampler<'static, Rc<Texture2d>>, simple_program: Program, texture: Rc<Texture2d>, } impl DefaultResourcePack { // 这里也不需要把facade的生命周期绑定到返回值了 fn new<F: Facade>(facade: &F) -> DefaultResourcePack { // 前面的regions逻辑完全不变 let sheet_dim: f32 = 16.0; let slot_size: f32 = 1.0 / sheet_dim; let mut regions = [TexRegion::new(0.0, 0.0, 0.0, 0.0); SHEET_TEX_ID_COUNT]; for &id in &ALL_SHEET_TEX_ID { let slot = sheet_tex_ord(id) as f32; let ord = sheet_tex_ord(id); regions[ord] = TexRegion::uvwh( (slot % sheet_dim) * slot_size, (slot - (slot % sheet_dim)) * slot_size, slot_size, slot_size, ) } // 把texture包装成Rc let image = image::load_from_memory(DEF_TEX_SHEET_BYTES) .expect("texture sheet load error") .to_rgba(); let image_dim = image.dimensions(); let image = RawImage2d::from_raw_rgba_reversed(&image.into_raw(), image_dim); let texture = Rc::new(Texture2d::new(facade, image).expect("texture creation error")); // 用Rc的引用来创建Sampler let sheet = Sampler::new(&texture).magnify_filter(MagnifySamplerFilter::Nearest); // program逻辑不变 let simple_program = Program::new( facade, ProgramCreationInput::SourceCode { vertex_shader: SIMPLE_SHADER_V, tessellation_control_shader: None, tessellation_evaluation_shader: None, geometry_shader: None, fragment_shader: SIMPLE_SHADER_F, transform_feedback_varyings: None, outputs_srgb: false, uses_point_size: false, }, ).expect("GLSL compilation error"); DefaultResourcePack { regions, sheet, simple_program, texture, } } }
原理:Rc是引用计数智能指针,结构体和Sampler各自持有一个指向Texture2d的引用,只要其中一个存在,Texture2d就不会被销毁,完美解决生命周期不匹配的问题。
方案2:用自引用结构体(适合严格需要所有权的场景)
如果你不想用Rc,可以用ouroboros crate创建自引用结构体,让Sampler直接引用结构体内部的Texture2d:
首先在Cargo.toml中添加依赖:
ouroboros = "0.15" # 版本号可以根据最新情况调整
然后修改代码:
use ouroboros::self_referencing; #[self_referencing] pub struct DefaultResourcePack { regions: [TexRegion; SHEET_TEX_ID_COUNT], simple_program: Program, // 持有Texture2d的所有权 texture: Texture2d, // 标注这个字段借用texture,使用结构体内部的生命周期'this #[borrows(texture)] #[covariant] sheet: Sampler<'this, Texture2d>, } impl DefaultResourcePack { fn new<F: Facade>(facade: &F) -> Self { // regions逻辑不变 let sheet_dim: f32 = 16.0; let slot_size: f32 = 1.0 / sheet_dim; let mut regions = [TexRegion::new(0.0, 0.0, 0.0, 0.0); SHEET_TEX_ID_COUNT]; for &id in &ALL_SHEET_TEX_ID { let slot = sheet_tex_ord(id) as f32; let ord = sheet_tex_ord(id); regions[ord] = TexRegion::uvwh( (slot % sheet_dim) * slot_size, (slot - (slot % sheet_dim)) * slot_size, slot_size, slot_size, ) } // 创建texture和program let image = image::load_from_memory(DEF_TEX_SHEET_BYTES) .expect("texture sheet load error") .to_rgba(); let image_dim = image.dimensions(); let image = RawImage2d::from_raw_rgba_reversed(&image.into_raw(), image_dim); let texture = Texture2d::new(facade, image).expect("texture creation error"); let simple_program = Program::new( facade, ProgramCreationInput::SourceCode { vertex_shader: SIMPLE_SHADER_V, tessellation_control_shader: None, tessellation_evaluation_shader: None, geometry_shader: None, fragment_shader: SIMPLE_SHADER_F, transform_feedback_varyings: None, outputs_srgb: false, uses_point_size: false, }, ).expect("GLSL compilation error"); // 用builder模式创建自引用结构体 DefaultResourcePackBuilder { regions, simple_program, texture, // 这里传入闭包,用来创建依赖texture的sheet sheet_builder: |texture| Sampler::new(texture).magnify_filter(MagnifySamplerFilter::Nearest), }.build() } }
原理:ouroboros的self_referencing宏会帮你处理自引用的生命周期安全,确保sheet永远只能引用结构体内部的texture,不会出现悬空引用。
内容的提问来源于stack exchange,提问作者Phoenix

