-
Notifications
You must be signed in to change notification settings - Fork 47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
References and pointers #49
Comments
extern "C" {
fn fun (arg: &'_ [mut] c_int);
} is basically: unsafe
fn fun (arg: &'_ [mut] c_int)
{
extern "C" {
fn fun (arg: *mut c_int)
}
fun(mem::transmute(arg))
} but where the transmute and the outer function are always "inlined", which requires that So, this requires three things:
So the main point here is the third one, which indeed depends on what the C code actually does / depends on the guarantees stated in the documentation of the function. If the pointee is mutated, then it is UB to feed it a pointer that originated from a shared reference to a
But if the mutation happens in a single-threaded context, it would be valid to feed it a
A fortiori (from
More about
|
damn, this is a good analysis of the issue. Indeed
yes, of course it's always unsafe to call C functions, worst case C function can always have a Can we agree that using immutable reference when calling C function is more unsafe than using raw now about lifetimes. You point out that if C function registers the pointer the lifetime bound wouldn't be sound and in the worst case scenario rust side could deallocate the location of the pointer before C dereferences it. To use |
as for the "nullable pointer optimization" I wouldn't use for wrapping references for the same reasons I wouldn't use references. They provide guarantees to the compiler which may happen to not be upheld. Function pointers don't have such guarantees and can be used as It would be interesting to analyze if |
I'd phrase it as "using Rust references for the signatures of |
yes, "human annotation" is lost. On the other hand you would get type system annotation that a null value can be passed for that particular argument. I can't say which "annotation" is of more value. The thing I don't know is how would I go about creating I find that safety-wise
yes, much more precise. :) |
And to go from from I agree with you that encoding the nullability is a big win, I was just "complaining" that it is a pity that Rust does not feature an official |
yes exactly, it's a pity there is no such feature in std.
yes, I understand. This was far more interesting discussion then I could have I hoped for. My takeaway is this:
I'm not sure which API do I find more pleasing for nullable pointers, |
|
@bjorn3 what you say is true, but I think you misunderstood the point. Please read the discussion. |
I meam that using |
@bjorn3 so it was me who misunderstood you 😁. Sorry, about that. hm, indeed references will coerce into raw pointers without explicit conversion. I don't think coercion will cause any extra unsafety, it'll still be as safe as using |
It's been a while since I've opened this issue. As I've come to learn, there is a trade off to using Consider the following example: extern "C" {
fn foo(var: *mut u8) {
// assume that the given value will be modified
}
fn bar(var: NonNull<u8>) {
// assume that the given value will be modified
}
}
fn main() {
foo(&mut 10u8);
// foo(&10u8); Luckily this doesn't compile
bar((&10u8).into());
} Surprisingly, this code compiles but the call to On the other hand shared reference doesn't coerce into I think that this kind of a bug would be a lot more prevalent(and a lot more difficult to locate) compared to null pointer related issues. It's unlikely that Rust users will create null pointers in their applications anyhow. The only situation where a sane Rust user would create null pointer is if it's used as a control flow value in the C function and would avoid null pointers in all other scenarios. Using |
Let me emphasize this: there is a difference whether you use NonNull documentation is clear on this:
It seems that compiler is allowed to depend on the assumption that value behind If you want to make your FFI library as safe as possible, you should prefer using |
If the There is no difference between |
@bjorn3 I'll also link a recently started discussion on this: rust-lang/unsafe-code-guidelines#257 on rust-lang. Would you still disagree? |
That issue is only about |
Wouldn't this be considered UB as well? main.c void foo(*int ptr) {
// due to some bug C code modifies the given pointer
(int)*ptr = 10;
} main.rs extern "C" {
fn foo(var: *const isize);
}
fn main() {
let var = 10isize;
foo(&var);
} |
and if we replace extern "C" {
fn foo(var: *mut isize);
} then it wouldn't be UB. |
No, it is UB in both cases because you derived the pointer from an read-only reference, not because you used |
Whether a pointer can be used to perform a write-dereference has nothing to do with it being
Actually it would, since the Rust code is using a shared reference ( With |
yes, sorry. This wouldn't compile, and would force you to go through |
To correct myself, this wouldn't be UB: main.c void foo(*int ptr) {
// due to some bug C code modifies the given pointer
(int)*ptr = 10;
} main.rs extern "C" {
fn foo(var: *mut isize);
}
fn main() {
let mut var = 10isize;
foo(&mut var);
} which is inconvenient if you want to use shared references throughout your code because you don't expect they'll be mutated. The point(hopefully correct) I was making is that you can have shared references and still remain protected from UB if you wrap your type in main.c void foo(*int ptr) {
// due to some bug C code modifies the given pointer
(int)*ptr = 10;
} main.rs extern "C" {
fn foo(var: *const UnsafeCell<isize>);
}
fn main() {
let var = UnsafeCell::new(10isize);
foo(&var);
} though it doesn't look very ergonomic. One could maybe wrap their value into newtype to make it more acceptable for use |
ok, I wasn't aware of this but I would refrain from using references in extern function declarations for safe FFI anyhow. This option we don't have to discuss |
I'd like to find how safe it is to use references in FFI but only as input arguments to extern C functions. Here is the section describing it.
I'm interested in situation where references are provided from Rust code to extern C functions, specifically:
foo.rs
foo.c
extern fun
cannot be called from rust with null values. If C function documents that doesn't accept null values this is acceptable. If it is required to be nullable, it can be wrapped inOption
.what if x is mutated inside
fun
? This seems like a safety issue, maybe an undefined behavior, data corruption or data race. It seems to me that using references is inherently unsafe when interfacing with C code. If that is so, should this section be modified to always advise against use of references in FFI?what about mutable references
&mut
? Would it be safe to use those in FFI? Are there possibly some lifetime related issues?The text was updated successfully, but these errors were encountered: