Maxim Mikhajlov
Maxim Mikhajlov

Reputation: 43

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 Points. Structures have new and random constructors.

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. 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.

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.

Upvotes: 3

Views: 345

Answers (2)

Finomnis
Finomnis

Reputation: 22466

I think your usage of references is misplaced here.

If Line must have references, I'd use a reference counted smartpointer instead.

The main problem is that if you store references in Line, Line does not own the Points. That means, you have to keep them alive externally.

This is the reason why your random constructor fails:

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.


Solution

There are two ways to solve this:

  • Use values instead of references (e.g. make Point cloneable)
  • Use reference counting smart pointers

In your case, as Point is a very trivial type, I'd go with the first option:

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)

This solution is just for reference.

As previously mentioned, it is way overkill for simple data structures, which should rather derive the Clone trait.

If you are in a multi-threaded environment, replace Rc<RefCell<Point>> with 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 } } }

Upvotes: 4

Netwave
Netwave

Reputation: 42678

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:

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.

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.

About the main, you just need to bind the Points to a variable, so they will live for the scope of 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

Upvotes: 1

Related Questions