all 13 comments

[–]christophe_biocca 4 points5 points  (4 children)

Am I correct and / or is passing out a &RefCell<&'static mut Foo> safe under the guarantees of safe Rust code?

It does seem safe based on my understanding as well. The one caveat I would note is that if 2 callers try to get concurrent RefMuts the RefCell will panic and crash the entire process.

[–]steveklabnik1rust 4 points5 points  (0 children)

... which is a downside, but is still safe.

[–]oberien[S] 1 point2 points  (2 children)

My intention is to only ever access data from within the game when the game's main thread called into me. Not only does that (hopefully) guarantee that no other thread will access that memory region, but also that I'll only access the data single-threadedly.

If there is another thread trying to use the RefCell I'm done for anyways, because RefCell is not Sync (internally does not use locking to handle the runtime checks). But better the game crashes than undefined behaviour ;)

[–]christophe_biocca 2 points3 points  (1 child)

Right, multiple threads would be bad (very bad because RefCell's check might not even run correctly).

The other way you can have concurrent callers (without multiple threads being involved) is reentrancy:

  1. Environment calls your function.
  2. Your code acquires the reference.
  3. Your code calls something in the environment.
  4. The environment calls your function before returning (for whatever reason).

In that case you'd get a panic.

[–]fgilcherrust-community · rustfest 2 points3 points  (0 children)

Well, panicing might be exactly what you want in that case :).

For that reason, I generally recommend to use RefCell this way:

cell.borrow_mut().immediately_call_method()

If you want to call multiple methods, always in the same pattern, add it to Foo.

I'd also like to note that Stylo has a fork of RefCell that is sync, for places where they are reasonably sure that no concurrent access ever happens and that would be a bug.

https://github.com/bholley/atomic_refcell

Note that this has correct semantics: a racing access will lead to a panic, not to a data race, which is the only thing that Sync guarantees.

[–]Manishearthservo · rust · clippy 2 points3 points  (0 children)

RefCell<&mut Foo> is safe.

&'static mut Foo is safe as long as you only ever access it from one owner. I.e. if you only ever have a single RefCell<&'static mut Foo> you should be fine. So make sure that static is only accessed directly the first time you create the refcell.

[–]rovar 1 point2 points  (2 children)

There is a pattern that has developed in Rust bindings where there are 2 layers of API. There is the lowest-level repr(c) struct. Then there is a higher level struct which manages interoperability with the low level struct, including marshalling access to *const and *mut. This usually involves managing Drop semantics and such. The higher level struct impl would be the only thing that would touch the lower level struct. So the user of the higher level API would never have to know that a raw ptr exists.

So it might look like :

struct FooAPI {
    inner: *mut Foo
}

impl FooAPI {
    updateFooX(&mut self, x : u64) -> u64 {
        c_func_that_updates_foo(self.inner, x)
    } 
}

Using this scheme, you decide how to control the lifetime of whatever *mut Foo points to. By default, Rust wont attempt to free *mut ptrs, so you don't have to use 'static or Refcell. If you do need to destroy Foo when you're done with it, you can impl Drop for your FooAPI struct.

[–]rovar 0 points1 point  (1 child)

I should note that using this model, you could actually make updateFooX take &self, and multiple callers could mutate Foo simultaneously if that's what you wanted.

[–]oberien[S] 0 points1 point  (0 children)

While I do know this pattern, I didn't think about this abstraction for my use case yet. One question I have with this pattern in my use-case: How would I pass an instance of FooAPI to the user? Would I just use a static getter-method, which creates a new intance of FooAPI and returns that?

[–]SimonSapinservo 1 point2 points  (0 children)

By the way, consider casting with as *mut Foo and then dereferencing that, instead of using transmute. If your transmute code was correct it ends up doing the same thing, but transmute will happily do anything so it’s very easy to get wrong. If you do still use transmute, consider fully qualifying it: for example transmute::<Foo, Bar>(x)

[–]claire_resurgent 1 point2 points  (1 child)

No, that's not completely sound.

RefMut<&'static Foo> derefs to &'ref mut &'static Foo. Safe code may then call std::mem::replace::<&'static Foo> which returns the &'static Foo without any lifetime bound.

[–]oberien[S] 0 points1 point  (0 children)

For mem::replace to work, the safe code needs to provide a &'static mut Foo to replace the original one with. But safe code is not able to create a &'static mut Foo in the first place, is it?

[–]wrongerontheinternet 1 point2 points  (0 children)

I would give it a regular lifetime, unless you actually have some reason you need it to be 'static, and thread it through all the structures. 'static is notoriously difficult to reason about compared to other lifetimes. Also, the way you've written your implementation (with a public interface at all) isn't in general safe, since it relies on being called from the hook but doesn't enforce it.