11

To be more specific, why doesn't Arc<T> implement from_raw with a dynamically sized T while Box<T> does?

use std::sync::Arc;

fn main() {
    let x = vec![1, 2, 3].into_boxed_slice();
    let y = Box::into_raw(x);
    let z = unsafe { Arc::from_raw(y) }; // ERROR
}

(play)

As pointed out in the comments, Arc::from_raw must be used with a pointer from Arc::into_raw, so the above example doesn't make sense. My original question (Is it possible to create an Arc<[T]> from a Vec<T>) remains: is this possible, and if not, why?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
John
  • 1,856
  • 2
  • 22
  • 33
  • 1
    Maybe that's a good thing: it would have been UB if that code compiled, because `Arc::from_raw` expects a pointer returned by `Arc::into_raw`. Nevertheless, the part where `from_raw` requires `T` to be sized might have a good answer. – E_net4 Jun 19 '17 at 18:04
  • @E_net4 Really? Then, writing `let x = Box::new(5);` instead in the example would be UB? – John Jun 19 '17 at 18:08
  • 2
    Note that `Arc::from_raw()` must only be used with a value returned from `Arc::into_raw()` because `Arc` places a header before the data pointer, and `Arc::from_raw` expects to find that header by looking immediately before the pointer you provide. – Lily Ballard Jun 19 '17 at 18:42
  • 1
    See also [How to build an Rc or Rc<[T\]>?](https://stackoverflow.com/q/31685697/155423). – Shepmaster Sep 18 '17 at 13:04

3 Answers3

15

As of Rust 1.21.0, you can do this:

let thing: Arc<[i32]> = vec![1, 2, 3].into();

This was enabled by RFC 1845:

In addition: From<Vec<T>> for Rc<[T]> and From<Box<T: ?Sized>> for Rc<T> will be added.

Identical APIs will also be added for Arc.

Internally, this uses a method called copy_from_slice, so the allocation of the Vec is not reused. For the details why, check out DK.'s answer.

Community
  • 1
  • 1
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
6

No.

First of all, as already noted in comments, you can't toss raw pointers around willy-nilly like that. To quote the documentation of Arc::from_raw:

The raw pointer must have been previously returned by a call to a Arc::into_raw.

You absolutely must read the documentation any time you're using an unsafe method.

Secondly, the conversion you want is impossible. Vec<T>Box<[T]> works because, internally, Vec<T> is effectively a (Box<[T]>, usize) pair. So, all the method does is give you access to that internal Box<[T]> pointer [1]. Arc<[T]>, however, is not physically compatible with a Box<[T]>, because it has to contain the reference counts. The thing being pointed to by Arc<T> has a different size and layout to the thing being pointed to by Box<T>.

The only way you could get from Vec<T> to Arc<[T]> would be to reallocate the contents of the vector in a reference-counted allocation... which I'm not aware of any way to do. I don't believe there's any particular reason it couldn't be implemented, it just hasn't [2].

All that said, I believe not being able to use dynamically sized types with Arc::into_raw/Arc::from_raw is a bug. It's certainly possible to get Arcs with dynamically sized types... though only by casting from pointers to fixed-sized types.


[1]: Not quite. Vec<T> doesn't actually have a Box<[T]> inside it, but it has something compatible. It also has to shrink the slice to not contain uninitialised elements.

[2]: Rust does not, on the whole, have good support for allocating dynamically sized things in general. It's possible that part of the reason for this hole in particular is that Box<T> also can't allocate arrays directly, which is possibly because Vec<T> exists, because Vec<T> used to be part of the language itself, and why would you add array allocation to Box when Vec already exists? "Why not have ArcVec<T>, then?" Because you'd never be able to construct one due to shared ownership.

DK.
  • 55,277
  • 5
  • 189
  • 162
1

Arc<[T]> is an Arc containing a pointer to a slice of T's. But [T] is not actually Sized at compile time since the compiler does not know how long it will be (versus &[T] which is just a reference and thus has a known size).

use std::sync::Arc;

fn main() {
    let v: Vec<u32> = vec![1, 2, 3];
    let b: Box<[u32]> = v.into_boxed_slice();
    let y: Arc<[u32]> = Arc::new(*b);
    print!("{:?}", y)
}

Play Link

However, you can make an Arc<&[T]> without making a boxed slice:

use std::sync::Arc;

fn main() {
    let v = vec![1, 2, 3];
    let y: Arc<&[u32]> = Arc::new(&v[..]);
    print!("{:?}", y)
}

Shared Ref Play Link

However, this seems like a study in the type system with little practical value. If what you really want is a view of the Vec that you can pass around between threads, an Arc<&[T]> will give you what you want. And if you need it to be on the heap, Arc<Box<&[T]>> works fine too.

SpamapS
  • 1,087
  • 9
  • 15
  • You won't be able to pass `Arc<&[T]>` or `Arc>` to another (unscoped) thread as they don't live for `'static`. Did you mean to write `Arc<[T]>` and `Arc>` respectively? In which case, the values in `Arc<[T]>` are already on the heap together with the counter, so the second allocation+indirection via `Box` is strictly superfluous. – Mihail Malostanidis Jun 20 '23 at 13:40