Copying Rc<Foo> to foreign/C memory

I think I see how to do this, but I want to double check here. Maybe there is a better idea.

I want to copy an Rc<Foo> or maybe an Rc<dyn Something> to an area of memory managed by another language. I think I can copy it into the memory like:

use std::ptr;
let rc: Rc<Foo> = ...
let mem: *mut Rc<Foo> = some_bytes as _;
ptr::write(mem, rc);

This will move it into the foreign memory, so the ref count will not go down, and the Rust memory will not be deallocated, even though (possibly) no Rc<Foo>s exist in Rust.

To get the value back out of C-land, I’m thinking I can use ptr::read:

let rc2: Rc<Foo> = ptr::read(mem);

But if I understand correctly, that will do a bitwise copy and nothing else, so I then need to use something like this: increment_strong_count, to account for this new rc2 value in Rust. Correct?

I think write and read should work. You shouldn't need increment_strong_count, since the value originally in rc is moved into rc2; there's not really a new Rc value. A move is a bitwise copy into the new location (and then the old location is no longer used, as enforced by the compiler), which is exactly what write and read seem to achieve.

You might also be interested in Rc::into_raw and from_raw.

The docs for read say it doesn’t move it.

1 Like

It doesn't work this way.

  1. For moving just the pointer (Rc is a pointer), you don't need any of these hacks. Use Rc::into_raw.

  2. You can't do anything with the heap memory of Rc. It's always Rust's memory, under Rc's own control. It has to stay that way. There's no non-buggy way to move standard library's Rc allocation to a non-Rust allocator [1].

You could create a non-Rc copy of the data elsewhere, if the type supports Copy or Clone: deref Rc<T> to &T, then make a copy of T if it allows. Rc::into_inner may work if there's only one instance.

It won't be possible with dyn types unless you can cast them to concrete types of the trait provides some dedicated escape hatch.

ptr::read can easily cause double-free vulnerabilities. It's a wrong tool here. Super dangerous.

There are safe functions for things you can do safely.


  1. support for custom allocators could change this in the future, but even then you'll need to create a fresh Rc with the right allocator from the start, you can't change an existing one, it's fundamentally impossible due to Rcs sharing an address for the refcount ↩︎

5 Likes

Look at the docs for ptr::write; neither dst nor src are dropped, and semantically src is moved to the location dst. And the docs for read say that "read creates a bitwise copy of T, regardless of whether T is Copy. If T is not Copy, using both the returned value and the value at *src can violate memory safety." In other words, if you want to use the returned value, then you should probably never again use the value at *src; you need to treat the value as though it were moved.

I should've said that moving a value is usually just a bitwise copy with move semantics enforced by the compiler. With all this unsafe code, a move is still just a bitwise copy, but you need to enforce the move semantics ("don't use the old location") yourself.

(Though Rc::into_raw and Rc::from_raw will probably help you avoid mistakes better than ptr::write and ptr::read.)

1 Like

If I'm not mistaken, Rc::from_raw can still cause a double-free, if, say, you call from_raw twice on the same pointer (akin to calling read twice) or call from_raw and then use the source pointer for something else (akin to calling read and then using *src in some other way). You still need to enforce the move semantics of from_raw yourself, and I don't think that's much different than ptr::read. Both require caution.

Yes, Rc::from_raw needs care.

Doing this via ptr functions need the same care, in addition to a whole another can of worms.

ptr::read creates a copy without invalidating the original, so you must ensure you leak either the source or the copy. It will also copy non-copyable types. It will change address of types that may have expected to stay at a fixed address. It's a very low-level dangerous primitive you shouldn't use unless you have to. Rc already has less dangerous functions for everything you need, so you don't need to reinvent them in low-level risky ways.

When I get the *const Foo from into_raw, is it okay if other clones of the Rc live on as normal Rc<Foo> values? Sounds like from_raw can only be used once, so … I’m guessing, no.

It is, if this *const Foo is indeed used as read-only pointer (modulo UnsafeCells).

That's right, but other clones of the same Rc don't depend on this at all.

If I use into_raw and put that pointer in C memory, and I use from_raw to get it back out, it seems that I can only do that once, to round-trip move it. But I’m trying to store the thing in the C memory like a property that I can read multiple times.

Maybe I need a layer of indirection. So I could make a Rust struct like:

struct CObjectProps {
    foo: Rc<Foo>,
    ...
}

Then I store a raw pointer to that and I can get copies of the Rc like (*raw_props).foo.clone()

Yes. But if you need to use a single pointer multiple times, you don't have to from_raw it each time, you can just reborrow it as &*ptr. from_raw is necessary only to drop the corresponding Rc, i.e. as the last thing you do when the pointer isn't going to be used anymore.

2 Likes

Other instances of Rc can live on. Rc::into_raw affects only one instance, not all of them. It doesn't do anything to the heap data, it only prevents one copy of Rc from being dropped and decrementing refcount, while also giving you an easy-to-use-in-C pointer.

Rc::into_raw is clever enough to offset the pointer to the data (so Rc<T> gives you *const T, not *const Rc<T>). This means you can use that pointer to access the data any number of times, without affecting the refcount.

You'd use Rc::from_raw on it only once when you completely stop using it on the C side, typically when the C object is freed/destroyed. Rc::from_raw brings it back to be Rust-owned, and Rust will drop it and decrement the refcount.

If you need to increment the Rc refcount in C, then use Rc::increment_strong_count

5 Likes