Skip to content
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

Reverting default unsafety #1901

Closed
wants to merge 1 commit into from
Closed

Conversation

petrochenkov
Copy link
Contributor

Foreign functions and statics are unsafe by default, provide a way to mark them as safe.

Rendered

@cramertj
Copy link
Member

Why not just wrap the foreign function in a safe, #[inline], Rust function? You could even make a macro to do this automatically.

@petrochenkov
Copy link
Contributor Author

@cramertj
The RFC mentions this, trivial wrapper functions are unnecessary boilerplate and the fix is as simple as accepting an unsafety modifier is couple more places.

@ranma42
Copy link
Contributor

ranma42 commented Feb 16, 2017

@petrochenkov Should we also exclude functions consuming pointer types? Rust can generate arbitrary pointers in safe code and it would probably be a bad idea to pass them to a C function (unless it does not actually dereference the pointer, which seems a pretty niche case).

}

#[repr(C)]
union LARGE_INTEGER {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd much rather change the (unstable) unions to require unsafe before them (always). Then in the future, we can add safe unions (like yours), which are checked by the compiler for safety.

[how-we-teach-this]: #how-we-teach-this

With a paragraph of text and an example.
The syntax is intuitive and mirrors existing `unsafe` functions.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have different opinions of intuitive ;). Double negatives are always confusing to me.

Maybe just add the unsafe before the extern, similar to unsafe impl?

@est31
Copy link
Member

est31 commented Feb 16, 2017

I'm not sure, but isn't one danger that !unsafe FFI functions can help you avoid the borrow checker?

E.g. take a function taking a mutable raw pointer. Right now this is unsafe by default. If you mark that function !unsafe then its safe to use it, and as creating raw pointers can happen anywhere, you can generate a raw pointer in safe code, pass it to that function and everything goes well.

This would make it not easier, but harder to use FFI as the feature would be a common pitfall for users unfamiliar to it. You could try to forbid any raw pointers, but what if its hidden somewhere in a nested struct.

So I don't think this feature is feasible. Also, while I agree to better tooling to generate FFI bindings to foreign languages, it should not come at a cost to make it easier to write unsafe code. Unsafe code should be hard to write so that it is avoided as often as possible.

@petrochenkov
Copy link
Contributor Author

Linking @nikomatsakis's and @wycats's thoughts regarding this issue.
rust-lang/rust#36247 (comment)

@petrochenkov
Copy link
Contributor Author

petrochenkov commented Feb 17, 2017

Regarding raw pointers.

Yes, this is a valid issue - people have to understand what they do when they write unsafe, this needs to be documented and explained. However, the main alternative (trivial wrappers) doesn't solve this problem either, so I still don't think this is a big enough reason to scrape the feature.
Clippy may help here, it already has a very similar lint - https://github.com/Manishearth/rust-clippy/wiki#not_unsafe_ptr_arg_deref.
(I'll add this to drawbacks though. I should also remove the clause about variadic functions because it goes in this lint-y territory as well.)

As a side note, foreign functions typically expect valid pointers, so they can usually use &T and Option<&T> instead of raw pointers.

@retep998
Copy link
Member

It's already simple enough to write an #[inline] wrapper, and if you're doing it enough that the boilerplate is considerable, you can write a macro that lets you both define the extern function and the wrapper at the same time. This RFC would not apply to most functions in winapi, because most functions do questionable things such as taking an array with a length, which has to be wrapped safely. I don't want to see anything that makes it easier for someone to accidentally make a function "safe" which really should be unsafe to call. I'd much rather see work towards making it easier to bind stuff in the first place, such as improving bindgen.

@est31
Copy link
Member

est31 commented Feb 18, 2017

I agree to the part of the RFC concerning statics, if mutating that global static is still unsafe.

@withoutboats withoutboats added the T-lang Relevant to the language team, which will review and decide on the RFC. label Feb 20, 2017
@nrc
Copy link
Member

nrc commented Feb 23, 2017

The RFC mentions this, trivial wrapper functions are unnecessary boilerplate and the fix is as simple as accepting an unsafety modifier is couple more places.

I guess my feeling is that trivial functions are boilerplate, but not unnecessary boilerplate. Moving from unsafe to safe is an important thing to do in a program and should not be done without pause, even where it seems obvious. IMO this RFC makes that too easy to do.

I also feel like the specific mechanism proposed adds complexity in a subtle way - it turns a 'marker' keyword into something more active. From the perspective of someone exploring the language it adds the following kinds of complexity:

  • which keywords can be negated? E.g., I write !pub?
  • what operations can I apply to unsafe can?
  • if a function is neither unsafe or !unsafe, what does it mean? Is that equivalent to one of the explicit forms?
  • can I write !!unsafe? Is that the same as unsafe?
  • what is the mental model for negatable and non-negatable keywords?

And so forth.

@nikomatsakis
Copy link
Contributor

I'd like to speak up in favor of this RFC -- or at least the intuition behind it. Specifically, while I am not a fan of the !unsafe syntax, I do think there is a use-case here that is not well-satisfied by today's Rust. To me, this has less to do with functions -- for which one can write a wrapper -- than with statics. It is pretty common in C to have statics that represent constants, but we have no way to wrap such a constant today, such that you can expose it to end-users. Every access to an extern static is unsafe.

Basically the best you can do is to have a function which reads the constant:

extern {
    static SENTINEL: SomeType;
}

fn sentinel() -> SomeType {
    unsafe {
        SENTINEL
    }
}

This is not great, both from an ergonomics POV (extra parens, etc) and because it's less efficient. Plus you may want two such functions, one for taking the address of the sentinel, etc.

I am not sure how I want to solve this problem, though. Just letting us declare extern functions as "not unsafe" might not be the right way to go. For the case of statics, one could imagine a more targeted fix, e.g. a way to declare those statics as being constant.

Another option might be improving unsafe itself -- I've been toying with the idea of unsafe functions that let you give more information about why the function is unsafe. Perhaps if we had this expanded form, you could also use that to declare that the function is safe. I don't have a very firm idea here though. Mostly I was thinking that if one were giving, as part of an unsafe, a list of conditions that ought to be checked, it might be possible for that list to be declared as explicitly empty or something like that.

I'll note one thing about function wrappers: they are indeed mostly equivalent, but not entirely. For example, when converting them to an address in memory, you will get a different result. And the compiler has to inline them and hence they generally incur more compilation time. Those problems alone doesn't seem like they merit the solution in this RFC (as opposed to statics, which have no compelling alternative), but it's worth mentioning.

@aturon aturon self-assigned this Feb 23, 2017
@nikomatsakis
Copy link
Contributor

We talked about this RFC extensively in the @rust-lang/lang meeting. I think there was a general consensus that:

  • Yes, we need some way to expose "C constants".
  • Yes, we need to make FFI more convenient and ergonomic.
  • But no, we probably don't want to accept this step, at least not right now.

I think the direction that I personally would prefer is to work on building a tool, either via build.rs or via procedural macros, that makes it really easy to create wrappers, shims, and in general the "glue" needed to connect to C (and eventually C++) code. I elaborated on these ideas in a comment on the roadmap thread, which I will excerpt here (the original comment goes into more detail):

What I do think is that we should work on reducing FFI boilerplate as much as we can. I think this starts just by taking a tool like bindgen and making it drop-dead simple to invoke (include! { ... }), perhaps leveraging progress on macros-1.1-style integration.

Once we have that, we can extend this include! macro to start filling in the gaps in our existing C integration. So for example dealing with macros properly is a big pain right now. Projects like Ruby using macros as inline functions, basically, so to properly bind to those you have to generate C shims that your Rust code can call. But wouldn't it be nice if include! could do this for you? (You'd probably have to direct it some, e.g. to tell it types involved.)

I think this tool will require a certain amount of language extensions to work. For example, I don't see how it could generate code that handles constant statics, as I mentioned, and I think some people may want the ability to have a "safe fn pointer" to C code that is known to be safe. But having the tool in place should let us more easily distinguish things that can be done via wrapping and what are the "core requirements" (I would expect the tool to make it easy to wrap unsafe C functions, at least as a starting point, with a safe wrapper.) Eventually I would want to integrate the tool more into rustc; I think for good C++ integration we will want to be able to provide feedback, e.g. to obtain the list of specializations we need for a template, or to generate shims that will handle C++ overloading. But that's far in the future anyway.

Sadly, I think a tool like this will need some champion to really get it going. @petrochenkov perhaps you are this person! :)

@petrochenkov
Copy link
Contributor Author

But no, we probably don't want to accept this step, at least not right now.

I'll close this then to save everyone's time.

a tool like this will need some champion to really get it going. @petrochenkov perhaps you are this person! :)

I don't think I'm ready to take such a responsibility now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

10 participants