简体   繁体   English

如何防止破坏 Rust 中对局部变量的引用?

[英]How to prevent destruction of references to local variables in Rust?

I have two structures.我有两个结构。 First is Point with two i32 coordinates, second is a Line with references to two Point s.首先是带有两个i32坐标的Point ,第二个是带有两个Point引用的Line Structures have new and random constructors.结构有newrandom的构造函数。

Required usage is:要求的用法是:


use sandbox::{Point, Line};

fn main() {
    let line = Line::new(&Point::new(1, 2),
                         &Point::new(1, 2));
    line.from; // error[E0716]: temporary value dropped while borrowed
    Line::random(10, 10); // error[E0515]: cannot return value referencing local variable `a`
}

And structs:和结构:

use rand::Rng;

pub struct Point {
    pub x: i32,
    pub y: i32,
}

pub struct Line<'line> {
    pub from: &'line Point,
    pub to: &'line Point,
}

impl Point {
    pub fn new(x: i32, y: i32) -> Point {
        Point { x, y }
    }
    pub fn random(x_max: i32, y_max: i32) -> Point {
        let x = rand::thread_rng().gen_range(0..=x_max);
        let y = rand::thread_rng().gen_range(0..=y_max);
        return Point::new(x, y);
    }
}

impl<'line> Line<'line> {
    pub fn new<'a>(from: &'a Point, to: &'a Point) -> Line<'a> {
        Line { from, to }
    }
    pub fn random<'a>(img_width: i32, img_height: i32) -> Line<'a> {
        let a = Point::random(img_width, img_height);
        let b = Point::random(img_width, img_height);
        Line::new(&a, &b)
        // error[E0515]: cannot return value referencing local variable `a`
        // returns a value referencing data owned by the current function
    }
}

Two errors occur.出现两个错误。 The first is related to the fact that the Point::new passed to Line is destroyed after Line::new is executed, so further usage is impossible.第一个与传递给LinePoint::newLine::new执行后被销毁有关,因此无法进一步使用。 It would be possible to take it out into separate variable, but this does not meet the requirements of the usage.可以将其取出到单独的变量中,但这不符合使用要求。

The second error is related to the fact that the generated Point::random needed to build Line::random is local, which means that after Line::random is executed, it also become inaccessible.第二个错误与构建Line::random Point::random random 是本地的有关,这意味着Line::random执行后也无法访问。

One possible solution is to use a heap( Box<T> ), but I haven't been able to figure out how to avoid destruction after the function completes.一种可能的解决方案是使用堆( Box<T> ),但我无法弄清楚如何在函数完成后避免破坏。

I think your usage of references is misplaced here.我认为您对引用的使用在这里放错了地方。

If Line must have references, I'd use a reference counted smartpointer instead.如果Line必须有引用,我会改用引用计数的智能指针。

The main problem is that if you store references in Line , Line does not own the Point s.主要问题是,如果您将引用存储在Line中, Line拥有Point s。 That means, you have to keep them alive externally.这意味着,你必须让它们在外部保持活力。

This is the reason why your random constructor fails:这就是random构造函数失败的原因:

pub fn random(x_max: i32, y_max: i32) -> Point {
    let x = rand::thread_rng().gen_range(0..=x_max);
    let y = rand::thread_rng().gen_range(0..=y_max);
    return Point::new(x, y);
}

As Point::new does not take ownership of x and y , the variables x and y cease to exist at the end of the random function.由于Point::new不拥有xy的所有权,因此变量xyrandom函数的末尾不再存在。


Solution解决方案

There are two ways to solve this:有两种方法可以解决这个问题:

  • Use values instead of references (eg make Point cloneable)使用值而不是引用(例如,使Point可克隆)
  • Use reference counting smart pointers使用引用计数智能指针

In your case, as Point is a very trivial type, I'd go with the first option:在您的情况下,由于Point是一种非常微不足道的类型,我会选择第一个选项:

use sandbox::{Line, Point};

fn main() {
    let line = Line::new(Point::new(1, 2), Point::new(1, 2));
    println!("{:?}", line);
    let line2 = Line::random(10, 10);
    println!("{:?}", line2);
}
use rand::Rng;

#[derive(Clone, Debug)]
pub struct Point {
    pub x: i32,
    pub y: i32,
}

#[derive(Clone, Debug)]
pub struct Line {
    pub from: Point,
    pub to: Point,
}

impl Point {
    pub fn new(x: i32, y: i32) -> Point {
        Point { x, y }
    }
    pub fn random(x_max: i32, y_max: i32) -> Point {
        let x = rand::thread_rng().gen_range(0..=x_max);
        let y = rand::thread_rng().gen_range(0..=y_max);
        return Point::new(x, y);
    }
}

impl Line {
    pub fn new(from: Point, to: Point) -> Line {
        Line { from, to }
    }
    pub fn random(img_width: i32, img_height: i32) -> Line {
        let a = Point::random(img_width, img_height);
        let b = Point::random(img_width, img_height);
        Line::new(a, b)
    }
}

Output:输出:

Line { from: Point { x: 1, y: 2 }, to: Point { x: 1, y: 2 } }
Line { from: Point { x: 9, y: 1 }, to: Point { x: 9, y: 1 } }

Solution #2 (with reference counters)解决方案 #2(使用引用计数器)

This solution is just for reference.此解决方案仅供参考。

As previously mentioned, it is way overkill for simple data structures, which should rather derive the Clone trait.如前所述,这对于简单的数据结构来说太过分了,它应该派生出Clone特征。

If you are in a multi-threaded environment, replace Rc<RefCell<Point>> with Arc<Mutex<Point>> .如果您处于多线程环境中,请将Rc<RefCell<Point>>替换为Arc<Mutex<Point>>

use std::{cell::RefCell, rc::Rc};

use sandbox::{Line, Point};

fn main() {
    let line = Line::new(
        Rc::new(RefCell::new(Point::new(1, 2))),
        Rc::new(RefCell::new(Point::new(1, 2))),
    );
    println!("{:?}", line);
    let line2 = Line::random(10, 10);
    println!("{:?}", line2);
}
use std::{cell::RefCell, rc::Rc};

use rand::Rng;

#[derive(Debug)]
pub struct Point {
    pub x: i32,
    pub y: i32,
}

#[derive(Debug)]
pub struct Line {
    pub from: Rc<RefCell<Point>>,
    pub to: Rc<RefCell<Point>>,
}

impl Point {
    pub fn new(x: i32, y: i32) -> Point {
        Point { x, y }
    }
    pub fn random(x_max: i32, y_max: i32) -> Point {
        let x = rand::thread_rng().gen_range(0..=x_max);
        let y = rand::thread_rng().gen_range(0..=y_max);
        return Point::new(x, y);
    }
}

impl Line {
    pub fn new(from: Rc<RefCell<Point>>, to: Rc<RefCell<Point>>) -> Line {
        Line { from, to }
    }
    pub fn random(img_width: i32, img_height: i32) -> Line {
        let a = Rc::new(RefCell::new(Point::random(img_width, img_height)));
        let b = Rc::new(RefCell::new(Point::random(img_width, img_height)));
        Line::new(a, b)
    }
}

Output:输出:

Line { from: RefCell { value: Point { x: 1, y: 2 } }, to: RefCell { value: Point { x: 1, y: 2 } } }
Line { from: RefCell { value: Point { x: 9, y: 1 } }, to: RefCell { value: Point { x: 4, y: 8 } } }

Well, this looks like you need for the points to be sometimes referenced and sometimes owned.好吧,这看起来像您需要有时引用和有时拥有这些点。 Rust provides Cow which comes in handy for this cases: Rust 提供了Cow在这种情况下派上用场:

use rand::Rng;
use std::borrow::Cow;

#[derive(Clone)]
pub struct Point {
    pub x: i32,
    pub y: i32,
}

pub struct Line<'line> {
    pub from: Cow<'line, Point>,
    pub to: Cow<'line, Point>,
}

impl Point {
    pub fn new(x: i32, y: i32) -> Point {
        Point { x, y }
    }
    pub fn random(x_max: i32, y_max: i32) -> Point {
        let x = rand::thread_rng().gen_range(0..=x_max);
        let y = rand::thread_rng().gen_range(0..=y_max);
        return Point::new(x, y);
    }
}

impl<'line> Line<'line> {
    pub fn new(from: &'line Point, to: &'line Point) -> Line<'line> {
        Line { from: Cow::Borrowed(from), to:  Cow::Borrowed(to)}
    }
    pub fn random(img_width: i32, img_height: i32) -> Line<'line> {
        let a = Point::random(img_width, img_height);
        let b = Point::random(img_width, img_height);
        Self {
            from: Cow::Owned(a),
            to: Cow::Owned(b)
        }
    }
}

Playground 操场

One possible solution is to use a heap(Box), but I haven't been able to figure out how to avoid destruction after the function completes.一种可能的解决方案是使用堆(Box),但我无法弄清楚在函数完成后如何避免破坏。

It does not, Box are still constrain to rust borrowing rules, and unless you leak it (making the references &'static ) will complain about the temporary values droped after the function scope.并非如此, Box仍然受制于 rust 借用规则,除非您泄漏它(使引用&'static ),否则会抱怨在函数范围之后丢弃的临时值。

About the main , you just need to bind the Point s to a variable, so they will live for the scope of main :关于main ,您只需要将Point s 绑定到一个变量,这样它们就会在main的范围内存在:

fn main() {
    let (from, to) = (Point::new(1, 2), Point::new(1, 2));
    let line = Line::new(&from, &to);
    line.from;
    Line::random(10, 10);
}

Playground 操场

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM