Casting `Rc<T>` to `Arc<T>`

Most of them would be enabled by polonius (see 2094-nll - The Rust RFC Book). I would do a formal proof of correctness, but this isn't that kind of language...

transmutes between various repr(Rust) structures, similar to the Rc to Arc transmute you want to do. I'd recommend avoiding them, or putting repr(C/transparent) on them for the cases where they are your own structs.

Pretty sure this isn't an option for any of the examples that exist in the project. It's mostly used in deepsize though, which is only for heap profiling and not enabled by default, so I'm not too fussed about it.

Some unsafe that's useless because there are safe apis to do the same things.

I'm certain this is false. Please provide more concrete examples. (Well, maybe your definition of "do the same thing" puts Vec::get and Vec::get_unchecked in the same class. If I can prove that a condition is always false, I don't want code for it in the binary. We probably disagree on this.)

Some ffi, which is fair.

This one actually surprises me, I didn't think I was doing any FFI

At any rate, I'd recommend running miri and cargo test as part of your CI. It's little effort and these are also very nice features of Rust :slight_smile:

I tried, but it seems difficult to run miri on full projects, I need file IO and other stuff. PRs welcome if you can find a way to get decent coverage with miri.

1 Like

You may want to switch between these two based on #[cfg(debug_assertions)] so that unoptimized builds will panic instead of doing something crazy. That way, if you do get unexpected behavior in future, you can easily build with checks in place to find the root cause.

For example, this:

impl DeepSizeOf for num::BigUint {
    fn deep_size_of_children(&self, _: &mut Context) -> usize {
        unsafe { &*<*const _>::cast::<Vec<u32>>(self) }.capacity()
    }
}

That's undefined behaviour, because a) BigUint makes no guarantees about its layout b) BigUint (currently) wraps a Vec<u64> (assuming you're on 64-bit) which is undefined behaviour to cast to Vec<u32>. I guess the latter will also give you the wrong results even if it happens to work.

Sadly num_bigint has no way to know the capacity, but you could use a library that does, e.g. rug. Which a) is a lot faster and b) would be safe.

1 Like

Yeah, I didn't want to write that code either. Would have been a lot nicer if BigUInt just had a capacity method. You would think that it's u64 on 64 bit, but no: BigUInt is composed of BigDigit which is only set to u64 if the u64_digit feature is enabled, and it's not enabled by default nor by my code. (You are right that there is a bug there, though, it should be ... * size_of::<u32>().)

I would like to use rug, num is not the best bignum library by a long shot, but it's LGPL and I'm not. (Actually, what I really need is a bignum library with small num optimization, because almost all the numbers I have to deal with are way less than 64 bit, but I need the fallback for correctness.)

That's fair. I actually did something like this recently: I have a let_unchecked macro for pattern matching without a discriminant check, and it uses cfg(debug_assertions) to decide whether to use unreachable! or unreachable_unchecked. But debug performance is actually surprisingly important for me, because when I'm developing I need to minimize (compile + run time) on my test programs, so if the interpreter loop isn't well optimized then everything suffers. Too bad the #[optimize] attribute hasn't landed yet...

1 Like

@Yandros @steffahn @mejrs I also benchmarked hybrid_rc on the same benchmark as before, resulting in a 0.2% improvement (1.682 s -> 1.675 s) which is almost certainly noise (it still has 2% variation between runs) but is good enough for me to use.

One thing that is a bit frustrating with the library is that the RcState trait is private, meaning that you can't even write down hybrid_rc::HybridRc<T, S> or write any functions generic over S since S: RcState is required by the type, so I might not be able to write those cloning functions after all, since all the functions are written for the Rc variant and I would have to make a copy of all the code to pass an Arc in.

3 Likes

PR such API addition? And benchmark its advantages in the interim :slightly_smiling_face:

1 Like

One thing I want to add: the split you're looking for is what I call "language UB" versus "library UB".

It's definitely "library UB" to transmute between Rc<T> and Arc<T>. It's even "library UB" to transmute between Rc<T> and Rc<U>, even when T and U are transmute-compatible.

However, it's only "language UB" when a property of the abstract machine is violated, i.e. an invalid value is observed, a dangling pointer is derefed, etc.

With these two, we can say that if the layouts of Rc<T> and Arc<T> happen to be compatible, then the transmute is not "language UB".

But language is still important: it's never sound, nor safe. Those both imply that there is a lack of "library UB;" that there's no chance of "language UB," even if every instance of "library UB" were taken advantage of by the library to invoke "language UB." Once you invoke "library UB," you give the library full permission to accidentally cause "language UB," because you've used it in an explicitly unsupported manner.

6 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.