【问题标题】:Borrowed value does not live long enough compiler error for struct借来的值对于结构来说没有足够长的时间存在编译器错误
【发布时间】:2015-10-18 04:03:31
【问题描述】:

我是这门语言的新手,仍在与借用检查器作斗争。我已经看到一些库使用 new() 函数,也就是不带参数的构造函数,并且它可以工作。基本上这意味着,返回的数据是在 new 的函数范围内创建的,并且不会在 new 的范围结束时被删除。

当我自己尝试这个时,借用检查器不会让这个代码通过。 除了将 i32 可变引用作为参数传递给构造函数之外,如何实现这一点。

我错过了什么吗?

#[derive(Debug)]
struct B<'a> {
    b: &'a i32
}

#[derive(Debug)]
struct A<'a> {
    one: B<'a>
}

impl<'a> A<'a> {
    fn new() -> A<'a> {
        // let mut b = 10i32;
        A {
            one: B{b: &mut 10i32}
        }
    }
}

fn main() {
    let a = A::new();
    println!("A -> {:?}", a);
}

编译器错误。

main.rs:15:19: 15:24 error: borrowed value does not live long enough
main.rs:15          one: B{b: &mut 10i32}
                                   ^~~~~
main.rs:12:20: 17:3 note: reference must be valid for the lifetime 'a as defined on the block at 12:19...
main.rs:12  fn new() -> A<'a> {
main.rs:13      // let mut b = 10i32;
main.rs:14      A {
main.rs:15          one: B{b: &mut 10i32}
main.rs:16      }
main.rs:17  }
main.rs:12:20: 17:3 note: ...but borrowed value is only valid for the block at 12:19
main.rs:12  fn new() -> A<'a> {
main.rs:13      // let mut b = 10i32;
main.rs:14      A {
main.rs:15          one: B{b: &mut 10i32}
main.rs:16      }
main.rs:17  }
error: aborting due to previous error

根据要求,这是我正在尝试使用的实际示例。 有这个 GUI 库(Conrod),它有一些实例化它的步骤。就像下面的例子一样。

let assets = find_folder::Search::ParentsThenKids(3, 3)
    .for_folder("assets").unwrap();
let font_path = assets.join("fonts/NotoSans/NotoSans-Regular.ttf");
let theme = Theme::default();
let glyph_cache = GlyphCache::new(&font_path).unwrap();
let ui = &mut Ui::new(glyph_cache, theme);

我的计划是将应用程序的绘图封装到一个结构中。那将有一个构造函数和一些辅助方法。为此,我必须有一个带有 conrod::Ui&lt;GlyphCache&lt;'a&gt;&gt; 类型实例的字段,这是上面 ui 变量的类型。

我认为向 main 添加东西(我的意思是在 main 中完成所有分配),可能不是最好的做事方式。

let mut app_ui = app::AppUi::new(); // This would encapsulate all of the above configuration lines.

// use the ui here
for e in evets {
    app_ui.handle_input();
    app_ui.render();
}

AppUi 的实现。它不完整,但应该显示总体思路。 为了确保我们在同一页面上,conrod::Ui&lt;GlyphCache&lt;'a&gt;&gt; 类型需要一个生命周期参数。我想拥有与结构相同的生命周期。我知道如何做到这一点的唯一方法是让结构本身获取生命周期参数,并将其传递给 UI 类型。

pub struct AppUi<'a> {
  pub ui: conrod::Ui<GlyphCache<'a>>,
  pub count: u16
}

impl<'a> AppUi<'a> {
  pub fn new() -> AppUi<'a> {
    let assets = find_folder::Search::ParentsThenKids(3, 3)
    .for_folder("assets").unwrap();

    let font_path = assets.join("FiraSans-Regular.ttf");
    let theme = Theme::default();
    let glyph_cache = GlyphCache::new(&font_path).unwrap();

    AppUi {
      ui: conrod::Ui::new(glyph_cache, theme),
      count: 0
    }
  }
}

=========================

我采用的解决方案,最终奏效了(至少目前有效)。是创建一个辅助函数,它会返回一个 glyph_cache 并使用它。我不确定它是否是惯用的 Rust,现在只会使用它。应该习惯于使用借用检查器。

pub struct AppUi<'a> {
  pub ui: conrod::Ui<GlyphCache<'a>>,
  pub count: u16
}

impl<'a> AppUi<'a> {
  pub fn new() -> AppUi<'a> {

    AppUi {
      ui: conrod::Ui::new(GlyphCache::new(&get_default_font_path()).unwrap(), Theme::default()),
      count: 0
    }
  }
}

pub fn get_default_font_path() -> PathBuf {
  find_folder::Search::ParentsThenKids(3, 3)
    .for_folder("assets")
    .unwrap()
    .join("FiraSans-Regular.ttf")
}

【问题讨论】:

    标签: rust borrow-checker


    【解决方案1】:

    那是因为&amp;mut 10i32 在您的程序中的寿命不够长。您指定one 将具有与a 相同的生命周期,但a 的寿命比one 长,因为在new 完成后i32 超出范围。另一方面,此代码将起作用:

    #[derive(Debug)]
    struct B<'a> {
        b: &'a i32
    }
    
    #[derive(Debug)]
    struct A<'a> {
        one: B<'a>
    }
    
    impl<'a> A<'a> {
        fn new(x: &'a mut i32) -> A<'a> {
            // let mut b = 10i32;
            A {
                one: B{b: x}
            }
        }
    }
    
    fn main() {
        let mut x = 10i32;
        let a = A::new(&mut x);
        println!("A -> {:?}", a);
    }
    

    注意x现在的寿命和a一样长,所以你的寿命很满意

    【讨论】:

    • 谢谢。我知道了。但是,这是否意味着没有办法在新方法中分配某些东西,并使其具有结构的生命周期?当 new 创建一个结构时,它会继续存在于更高的范围内。字段的生命周期不能与结构的生命周期相匹配吗?这是否意味着所有返回的字段都必须在堆栈上。
    • @6D65 您不能保留对超出范围的内容的引用,您编写的结构要求引用与结构一样长,因为它应该。我不明白你为什么要在 new 中创建一个对象并将 reference 存储在你的结构中;在这种情况下,您应该移动值并存储对象而不是引用
    • 在您的另一个示例中,glyph_cache 与您的第一个示例一样超出了范围。您需要将 glyph_cache 引用传递到 new 中,或者不获取 glyph_case 的引用并改为移动值
    • “移动值”是指将 glyph_cache 作为值传递给 new?
    • @6D65 是的,这样 glyph_cache 不会超出范围,因为您已经移动了它的值。
    【解决方案2】:

    理解这一点的关键是&amp; 引用表示借用,而不是拥有的值。生命周期注释不控制值的生存时间;他们只跟踪以确保借用引用的所指对象比借用引用本身的寿命更长,因此取消引用它总是有效的。

    借用引用可以引用堆栈(静态分配的内存)或堆(动态分配的内存)中的值。堆栈上的值具有相当明显的生命周期;从变量初始化开始,直到这些值从堆栈中弹出时块结束。

    堆上的值归栈上的指针所有;因此它们的生命周期由拥有它们的堆栈上的指针决定。但是,所有权可以在不同变量之间移动,因此如果将引用它们的指针的所有权从一个堆栈变量移动到另一个堆栈变量,您实际上可以拥有更灵活的这些值的生命周期。

    如果你编写一个带有如下签名的函数:

    fn new() -> A<'a> {}
    

    你的意思是你将返回一个A,其中包含的引用有一些生命周期'a,这是由调用者决定的;但你不能这样做,因为你没有得到任何这样的参考作为输入。您不能从 new 函数中生成具有任意输入生命周期的值。

    new 函数中,您通常想要返回的是拥有的 值;或者可能是基于某些输入参数的借用值,但您需要提供这些引用作为输入参数。

    如果您描述更多关于您正在尝试做的事情,而不仅仅是提供一个玩具示例,这可能会有所帮助。您可以在这里尝试做几件可能的事情,但是仅通过玩具示例很难确定要描述的内容。返回包含引用的对象的目的是什么?是不是可以让对象在堆上分配,因此在传递它时只需要在单个指针周围移动而不是复制整个值?在这种情况下,您可能需要BoxVec。是否可以引用一些堆栈分配的变量?然后,您需要在包含堆栈帧中分配它,并将具有该生命周期的引用传递给您的函数,以便有一个具有适当生命周期的变量可供引用。如果你只是想返回一个包含整数的对象,那么你可以让对象直接包含整数,而不是包含对它的引用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-20
      • 1970-01-01
      • 1970-01-01
      • 2015-04-12
      • 2023-03-09
      相关资源
      最近更新 更多