I am experimenting rust by implementing a simple game, and quickly ran into a situation where I find it is hard to decide what is a better approach and I think it is related to a broader question about references and lifetimes in Rust, hopefully, someone has better experience on this.
Assuming I have a Game that represents the game world, and GameObject is the trait for all item in the game. And a GameState to keep track of object collisions for every update, Collision will need to hold reference to other GameObject in order to tell which ones collide, in order to do that I will need to add a lifetime to the Collision then add lifetimes to GameState and then add to Game if I want the Game object to hold the state, Or I can maybe copy the GameState for every update tick.
trait GameObject {
fn draw(&self, renderer: &Renderer);
fn update(&mut self, state: &GameState, delta: f64);
}
struct Collision<'a> {
obj1: &'a Box<dyn GameObject>,
obj2: &'a Box<dyn GameObject>
}
struct GameState<'a> {
// other states...
collisions: Vec<Collision<'a>>,
}
struct Game {
items: Box<dyn GameObject>,
}
impl Game {
fn draw(&self, renderer: &Renderer){
for item in &self.items {
item.draw(render);
}
}
fn update(&mut self, delta: f64){
// create a state everytime? or hold a reference to it?
for item in &self.items {
item.update(delta);
}
}
}
So that's where I am 100% sure what would be the best approach, or in general, once we give a lifetime to an object, then any other object holds this object will need lifetime types, not maybe we should reorganize the code structure to avoid lifetimes? Thanks.
My advice would be to use lifetimes inside temporary structs but avoid them for structs you want to keep around. So if you create a GameState for every frame, add to it, use it and destroy it, that's ok. But if you want to keep it alive for many frames, then those references will be a problem, because you will be keeping your objects borrowed all the time.
If you have complex references between your objects, there are several solutions that do not require lifetimes and persistent borrows:
Use Rc<dyn GameObject> instead of Box and store Weak versions of the pointer everywhere except in the main container.
Store the objects in a SlotMap and use a key to refer to the objects.
Store the objects in a Vec and use the index to refer to the objects. It is just like the SlotMap but simpler. Still useful for simpler cases such as if your objects are just created once and then never added or removed.
Use an Entity-Component-System (ECS) library, such as Bevy suggested by #tadman in the comment above.
Related
Is it safe to transmute a shared reference & to a strong Arc<T> into a shared reference & to a Weak<T>?
To ask another way: is the following safe function sound, or is it a vulnerability waiting to happen?
pub fn as_weak<'a, T>(strong: &'a Arc<T>) -> &'a Weak<T> {
unsafe { transmute::<&'a Arc<T>, &'a Weak<T>>(strong) }
}
Why I want to do this
We have an existing function that returns a &Weak<T>. The internal data structure has changed a bit, and I now have an Arc<T> where I previously had a Weak<T>, but I need to maintain semver compatibility with this function's interface. I'd rather avoid needing to stash an actual Weak<T> copy just for the sake of this function if I don't need to.
Why I hope this is safe
The underlying memory representations of Arc<T> and Weak<T> are the same: a not-null pointer (or pointer-like value for Weak::new()) to an internal ArcInner struct, which contains the strong and weak reference counts and the inner T value.
Arc<T> also contains a PhantomData<T>, but my understanding is that if that changes anything, it would only apply on drop, which isn't relevant for the case here as we're only transmuting a shared reference, not an owned value.
The operations that an Arc<T> will perform on its inner pointer are presumably a superset of those that may be performed by a Weak<T>, since they have the same representation but Arc carries a guarantee that the inner T value is still alive, while Weak does not.
Given these facts, it seems to me like nothing could go wrong. However, I haven't written much unsafe code before, and never for a production case like this. I'm not confident that I fully understand the possible issues. Is this transmutation safe and sound, or are are there other factors that need to be considered?
No, this is not sound.
Neither Arc nor Weak has a #[repr] forcing a particular layout, therefore they are both #[repr(Rust)] by default. According to the Rustonomicon section about repr(Rust):
struct A {
a: i32,
b: u64,
}
struct B {
a: i32,
b: u64,
}
Rust does guarantee that two instances of A have their data laid out in exactly the same way. However Rust does not currently guarantee that an instance of A has the same field ordering or padding as an instance of B.
You cannot therefore assume that Arc<T> and Weak<T> have the same layout.
I am designing some struct with a prepare-commit pattern in rust, which mostly looks like this after simplified:
struct Data {
a: usize,
}
struct Adder<'a> {
data: &'a mut Data,
added: usize,
}
struct Holder<'a> {
data: &'a Data,
}
impl<'a> Adder<'a> {
fn add_once(&mut self) {
// Needs added to be mut, but not data
self.added = self.added + self.data.a;
}
fn commit(&mut self) {
// Needs data to be mut, but not added
self.data.a = self.added;
}
}
fn main() {
let mut d = Data { a: 1 };
let h = Holder { data: &d };
let mut ad = Adder {
data: &mut d,
added: 0,
};
ad.add_once();
ad.add_once();
println!("{}", h.data.a); // Cannot borrow here
ad.commit();
println!("{}", ad.data.a);
}
This snippet does not compile. The problem with the code snippet is that since Adder is borrowing data as mutable, upon the creation of Adder struct, all other immutable borrows (e.g.Holder here) could not live past. However, the add_once calls really do not need to borrow data mutably, only commit does that. Ideally there should be some way in rust to let the compiler know that add_once only needs added as mutable but not data, so that Holder could live past add_once calls but not commit call.
I do know two possible current solutions to this, however I think they both have drawbacks:
I could wrap fields of Adder in RefCell and borrow entire Adder immutably, only borrows mutably when needed. The problem with this approach is that RefCell checks borrowing dynamically, when in this case it really does not need to be that way since all lifetime bounds should be known at compile time, just different parts of a struct could have different bounds.
I could do unsafe blocks to convert references to raw pointers and do whatever I want. However as far as I think about it I could not find a way to do it without disable all borrow checks altogether (I don't really know how to do unsafe rust properly). And I want a borrow check so that if Holder lives past commit the compiler would still give a proper borrow error
So summary of questions:
Is there a static and safe way in rust to borrow different parts of self, with different mutability?
If not, is there a unsafe boilerplate to do so, still ensuring the proper borrow check as described above? (and if there is an existing crate for this)
What is the best practice in general for these situations?
Is there any RFC addressing this issue from rust language level?
Is there a static and safe way in rust to borrow different parts of self, with different mutability?
No.
If not, is there a unsafe boilerplate to do so, still ensuring the proper borrow check as described above? (and if there is an existing crate for this)
You can use unsafe for that, but it will require you to work with raw pointers only. You may be able to encapsulate it in a safe API, but it's unlikely that inside the code you'll have proper automatic verification. For this reason I also don't believe there is a crate for that, at least I don't know one.
What is the best practice in general for these situations?
It depends.
I would use RefCell (or other interior mutability primitives such as Cell - note that if you can use Cell it is actually zero-cost) for that generally, and use unsafe code if profiling shows a bottleneck. However, this kind of issues many times arises from improper API design. So first I'll see if I can redesign my API so the problem will not exist.
Is there any RFC addressing this issue from rust language level?
Not today, and I think it is very unlikely in the future.
There was something similar suggested while working on two phase borrows: https://github.com/rust-lang/rust/issues/49434. The idea was that any mutable reference will start as shared and only "upgrade" when it is actually used. In can be extended to "upgrade" only when a mutable reference is actually required, and this is basically what you propose.
However, the effect of two-phase borrows is completely local. For your code to work, we will have to need some way to represent time-aware mutable reference in a type. I don't see that happening.
In addition, two-phase borrows work poorly with Stacked Borrows. #RalfJung ended up with just using raw pointers when two phase borrows are involved. Using them for everything will probably mean poor optimization opportunities (although maybe future models, or even Stacked Borrows itself, will find a way to handle that).
I am building a graphics engine from scratch in Rust.
I have a struct Point:
struct Point {
x: i32,
y: i32
}
and a triangle struct, which is just three points:
struct Triangle (Point,Point,Point)
.
I made a trait Engine that I will later implement with SDL
trait Engine {
fn draw_triangle(&self,tri:Triangle){
self.draw_line(tri.0,tri.1);
self.draw_line(tri.1,tri.2); // <-
self.draw_line(tri.2,tri.0);
}
fn draw_line(&self,p1:Point,p2:Point)
}
I get an error on line with an arrow: use of moved value tri.1, value used after move.
I know this has something to do with references and ownership, and have experimented changing things, but I just don't know what I am doing.
I have searched and searched, but to no avail: I cannot comprehend.
Can anyone tell me why this is not working? More than a solution, I wish to understand
On the first draw_line call, you pass two points tri.0 and tri.1. This "moves" the variables into the draw_line function, and they cannot be used afterwards. There are two ways to get around doing this.
Firstly, you can copy (or clone) the points when calling the functions by simply adding #[derive(Clone, Copy)] above the struct Point line. This allows Points to be either cloned explicitly (by using the .clone() method) or copied implicitly. This means that the data will be copied every time you pass it into a function, which in this case isn't a big deal. However, for larger structs, this will want to be avoided and as such you can use the following method.
Secondly, you can pass the Points as a reference instead of by value. This can be accomplished by changing the draw_line signature to accept a Point reference as follows:
fn draw_line(&self, p1: &Point, p2: &Point);
However, this means you now also need to pass the Points by reference, leading to the following trait definition:
trait Engine {
fn draw_triangle(&self, tri: Triangle) {
self.draw_line(&tri.0, &tri.1);
self.draw_line(&tri.1, &tri.2);
self.draw_line(&tri.2, &tri.0);
}
fn draw_line(&self, p1: &Point, p2: &Point);
}
This leads to the Points themselves not being passed into the function, but instead a reference to a single Point - meaning the structs don't need to be copied six times (two for each function call). If this method was called a lot, or the structs were larger, this could be a significant speedup.
The following chapter of the rust book provides a thorough explanation of ownership, references, and borrowing: https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html.
In the following code, I am trying to change the value of a refcounted object by calling one of its methods:
use std::rc::Rc;
fn main() {
let mut x = Rc::new(Thing { num: 50 });
x.what_to_do_to_get_mut_thing().change_num(19); //what do i do here
}
pub struct Thing {
pub num: u32,
}
impl Thing {
pub fn change_num(&mut self, newnum: u32) {
self.num = newnum;
}
}
I am using the get_mut function to achieve this, but I don't know if this is a standard way to accomplish this.
if let Some(val) = Rc::get_mut(&mut x) {
val.change_num(19);
}
The documentation for Rc says:
See the module-level documentation for more details.
Which has this text:
This is difficult because Rc enforces memory safety by only giving out shared references to the value it wraps, and these don't allow direct mutation. We need to wrap the part of the value we wish to mutate in a RefCell, which provides interior mutability: a method to achieve mutability through a shared reference. RefCell enforces Rust's borrowing rules at runtime.
It then demonstrates how to use it.
If you didn't read the API documentation, you might have instead chosen to read the entire chapter about Rc in The Rust Programming Language. It has this to say:
Via immutable references, Rc<T> allows you to share data between multiple parts of your program for reading only. If Rc<T> allowed you to have multiple mutable references too, you might violate one of the borrowing rules discussed in Chapter 4: multiple mutable borrows to the same place can cause data races and inconsistencies. But being able to mutate data is very useful! In the next section, we’ll discuss the interior mutability pattern and the RefCell<T> type that you can use in conjunction with an Rc<T> to work with this immutability restriction.
Applying this new knowledge to your code:
use std::{cell::RefCell, rc::Rc};
fn main() {
let x = Rc::new(RefCell::new(Thing { num: 50 }));
x.borrow_mut().change_num(19);
}
See also:
How do I share a mutable object between threads using Arc?
Situations where Cell or RefCell is the best choice
What is the difference between Rc<RefCell<T>> and RefCell<Rc<T>>?
When I can use either Cell or RefCell, which should I choose?
Need holistic explanation about Rust's cell and reference counted types
I am using the get_mut function
It's very unlikely that you want to use this.
See also:
Explain the behavior of *Rc::make_mut and why it differs compared to Mutex
This isn't necessarily the OP's question, but a huge pitfall that can lead to issues with Rc<RefCell<X>> can be tracked down to the BorrowMut trait.
If you have run into the issue where calling .borrow_mut() on an Rc<RefCell<X>> returns another Rc<RefCell<X>>, make sure that the BorrowMut trait is not being used at the top of your script.
Rust-Analyzer automatically adds it in, and it completely changes the behavior of that function. I learned about this after many hours of debugging when I stumbled upon this issue.
I have the following code:
pub trait MyTrait {
pub fn do_something(&self);
}
If I want a struct A to have a field a that implements the trait MyTrait, there are 2 options:
pub struct A<'a> {
a: &'a MyTrait
}
or
pub struct A {
a: Box<MyTrait>
}
But on Difference between pass by reference and by box, someone said:
Really, Box<T> is only useful for recursive data structures (so that
they can be represented rather than being of infinite size) and for
the very occasional performance optimisation on large types (which you
shouldn’t try doing without measurements).
Unless A implements MyTrait, I'd say A is not a recursive data structure, so that makes me think I should prefer using a reference instead of a box.
If I have another struct B that has a reference to some A object, like this:
pub struct A<'a> {
a: &'a MyTrait
}
pub struct B<'a, 'b: 'a> {
b: &'a A<'b>
}
I need to say that 'b is larger than 'a, and according to the documentation:
You won't often need this syntax, but it can come up in situations
like this one, where you need to refer to something you have a
reference to.
I feel like that's a bad choice too, because the example here is really simple and probably doesn't need this kind of advanced feature.
How to decide whether I should use a reference or a box then?
Unfortunately, the quote you used applied to a completely different situation.
Really, Box<T> is only useful for recursive data structures (so that they can be represented rather than being of infinite size) and for the very occasional performance optimisation on large types (which you shouldn’t try doing without measurements).
Is speaking about using either of MyEnum or Box<MyEnum> for data members:
it is not comparing to references,
it is not talking about traits.
So... reset your brain, and let's start from scratch again.
The main difference between Box and a reference is ownership:
A Box indicates that the surrounding struct owns the piece of data,
A reference indicates that the surrounding struct borrows the piece of data.
Their use, therefore, is dictated by whether you want ownership or borrowing, which is a situational decision: neither is better than the other in the same way that a screwdriver and a hammer are not better than the other.
Rc (and Arc) can somewhat alleviate the need to decide, as they allow multiple owners, however they also introduce the risk of reference cycles which is its own nightmare to debug so I would caution over overusing them.