Impl Send for Vec<*const c_char>?

I'm using a create where a method returns a Vec<*const std::os::raw::c_char>, which I want to move to a new thread (the underlying data should to persist for the lifetime of the program), preferably unchanged (since I want to continue using it there as a Vec<*const std::os::raw::c_char>).

However doing so results in a compiler error *const i8 cannot be sent between threads safely -> Rust Playground

I found various resources online about the general issue, but I'm still unsure what would be a clean way (code-wise) to resolve this case.

  • Do I need to work with a wrapper struct that implements Send (essentially copying the *const c_chars)?
  • Do I have use a Vec<String> as an intermediary struct? (safe but not very efficient)
  • Is there another way that I'm not aware of?

Your vector is already Send as can be verified by this program:

fn check_send<T: Send>() {}

fn main() {
    check_send::<Vec<std::os::raw::c_char>>();
}

There may be some other field or variable containing a raw pointer. Can you paste the entire output from the compiler?

Sorry, I didn't paste the correct type when creating the post (should be *const c_char instead of c_char), I updated it accordingly.

I would go for wrapper struct.

use std::os::raw::c_char;

struct CharPtrVec {
    vec: Vec<*const c_char>,
}

unsafe impl Send for CharPtrVec {}

Ah thanks, for some reason I thought I had to create the wrapper struct around *const c_char, but this makes it considerably easier.

I take it thats the established way of resolving this kind of issue then (no way to directly use Vec<*const c_char>)?

One thing to understand in Rust is that the property of a given type (e.g., Vec<*const c_char>) implementing a trait (e.g., [to be safe to] Send [across threads]) is a global property, one which applies to all the crates. And you can't claim to make all instances of Vec<*const c_char> to be safe to Send across threads, since there could be crates out there which could rely on that not holding for their instances of Vec<*const c_char>.

Hence why you need to use a new type: your new type, to which you can legitimately attach the semantics you want since you're its "owner" / its creator.


Back to the main issue, newtyping the Vec does not seem to be the most correct thing to do: there is nothing telling the type system that the *const c_char instances in that Vec should have any properties whatsoever, which makes your code more brittle to refactorings; if you are receiving *const c_char from the FFI which represent 'static / forever-valid / never-dangling slim pointers to a UTF-8 (e.g., ASCII) sequence of bytes which is null terminated, then I recommend that you define your own newtype around *const c_char alone, expressing that property, or that you pick one from a crate that has already done that (such as char_p::Ref<'static>, which can thus be Send. From there, a Vec<char_p::Ref<'static>> will be Send as well).

5 Likes

Hence why you need to use a new type: your new type, to which you can legitimately attach the semantics you want since you're its "owner" / its creator.

Thanks for the explanation, it makes sense, but it just seems kind of clunky to me (at least the wrapper code is pretty light).

However if I were to wrap it around *const c_char as you suggested, I would have to explicitly take the elements from Vec<*const c_char> to a Vec<CharPtrWrapper> and vice versa, instead of just passing the entire Vec<*const c_char> with @alice's wrapper struct, right?

That would seem kind of overkill to me, especially because I just need to do this for one small instance (I'm not even considering pulling in a new crate for this).

#[repr(transparent)] will guarantee ABI compaitibility.

I assume you were responding to this?

I would have to explicitly take the elements from Vec<*const c_char> to a Vec<CharPtrWrapper> and vice versa

Could you perhaps give an example of how an implementation of this would look like? The naive approach doesn't work for me -> Rust Playground

You have to use Vec::from_raw_parts to convert from one to the other. Rust Playground

1 Like

Thanks, though thats exactly the kind of extra code that seems overkill to me for this kind of problem (I have no doubt that its necessary for all kinds of reasons), so I'll stick with the original solution.

If you're dealing with FFI, you use your transparent newtype on the Rust side and the underlying type on the foreign side, so you'd typically always have a Vec of the newtype on the Rust side. There is more boilerplate if you need the Vec of *const c_char specifically on the Rust side for some reason, in which case it may not be worth it.

1 Like

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.