Reputation: 43
I have two structures. First is Point
with two i32
coordinates, second is a Line
with references to two Point
s. 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
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 Point
s. 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.
There are two ways to solve this:
Point
cloneable)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 } }
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
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)
}
}
}
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 Point
s 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);
}
Upvotes: 1