-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Unsafe Indexing and Slicing #392
Conversation
db1a35c
to
955ead9
Compare
cc @gankro |
I really like this proposal: it neatly solves some of the API consistency and ergonomics concerns around some very common uses of unchecked/unsafe code: skipping bounds checks. I see very little downside here. I am slightly hesitant about not requiring this to be done in an As an aside, this proposal doesn't quite support the ergonomics @gankro wanted for his raw reform RFC, since when working with raw slices you'd still be required to write |
This is a really cool idea! As a possible way to bridge some of the ergonomics concerns posed by @aturon perhaps we consider the following rules (replace Slice and UnsafeSlice with your operator of choice):
I'm ambivalent about making unsafe indexing "safe". The strikes me as arguing I wonder if we should consider introducing guidelines (if we haven't already?) about where to precisely delimit safety. For instance, given code like the following, should we prefer:
Or:
What are we trying to express here? To me, there's the possible suggestion that all of the former must be refactored/rearranged with care, while the latter suggests that the operations are all totally independent, and can be rearranged freely without concern for the others. I'm also thinking writing all of |
The duplicate-unsafe looks very ugly to me, and unchecked indexing is a such a distinct class of unsafety that wrapping it in an unsafe block would encourage the use of additional unsafe operations (e.g. the original insertion sort working with raw slices). Also, people would probably like to add Not providing the |
|
||
In addition, the syntax would be slightly ambigious with non-block | ||
unsafe expressions if they are ever introduced | ||
(```a[unsafe ptr::read(x)]```). Giving this syntax precedence seems |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this be replaced with unsafe unsafe ptr::read(x)
, because that seems strange. Considering the relative likelihood of unsafe
expressions being added in the future, we should have a plan to deal with this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be replaced by unsafe a[unsafe ptr::read(x)]
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not convinced it can/I don't see how that syntax resolves the ambiguity. The unsafe
before a allows additional unsafe operations inside the brackets? That seems extremely weird to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unsafe a[unsafe ptr::read(x)]
would be equivalent (given unsafe expressions) to this RFC's unsafe { a[unsafe ptr::read(x)] }
, with the ptr::read
being allowed by the unsafe
block/expression
It would be nice to be able to add #[unchecked_slicing]
unsafe {
let first = s.read(len-1);
s[1..].copy(s[ ..len-1]);
s.write(0, first);
} EDIT: This idea is already mentioned under alternatives |
My only concern is whether this might lead to a culture where people add (A full Again, just to be clear: this is only a concern, and not necessarily an objection. |
@glaebhoerl, I think as long as we are not doing implicit indexing/slicing without bound checks, we'll be fine. If a programmer throws |
I don't like this because of the drawbacks mentioned. It especially bothers me that this approch is completely un-extensible (what about unsafe versions of other operators?). A much better and extensible approach would be to create ad-hoc operator overloads, with an unsafe variant of indexing implemented on an adapter type. The syntax would be: let vec_raw = vec.into_raw(); // or some other name
let val = unsafe{ vec_raw[idx] }; If game developers balk at putting |
Your proposal ends up with forcing people to use |
Maybe struct UnsafeMutAccessor<'a, T:'a> {
slice: &'a mut [T]
}
// impl the unsafe indexing traits on UnsafeMutAccessor
trait UnsafeMutAccess<T> {
fn as_unsafe_mut<'a>(&'a mut self) -> UnsafeMutAccessor<'a, T>;
}
impl<'b, T> UnsafeMutAccess<T> for &'b mut [T] {
fn as_unsafe_mut<'a>(&'a mut self) -> UnsafeMutAccessor<'a, T> {
UnsafeMutAccessor{ slice: *self }
}
}
impl<'b, T> UnsafeMutAccess<T> for Vec<T> {
fn as_unsafe_mut<'a>(&'a mut self) -> UnsafeMutAccessor<'a, T> {
UnsafeMutAccessor{ slice: self.as_mut_slice() }
}
} |
I assumed @SiegeLord was proposing that intermediary representation all along. It's something I've been mulling over since the no-lifetime issue was noted on my RFC. It's definitely worth seriously considering, although it seems unfortunate to have all of these "modes" of slice. Maybe slices just are important enough to justify it, though. Note that "coerce into a slice to get unsafe indexing" doesn't work on e.g. RingBuf, which can't be represented (in general) as a single slice. Maybe if it had a method that returns two slices? |
This doesn't seem the best way to solve the common case where the programmer wants to avoid the overhead of bounds checking:
A "perfect" solution would be a way to specify intent to the compiler, eg. "unsafe constraint(i >= 0 && i < length)", and then have it automatically omit bounds checking where the constraint implies the bound check would succeed. Eventually the compiler could even do static analysis to try to determine if the constraint holds, which would allow safe constraints, but that is obviously very difficult... An imperfect but more practical solution would be to have an unsafe attribute which when applied to a variable declaration causes all indexes into that variable to omit bounds checking. This attribute would only have an effect when triggered with a particular compiler configuration, so that boundary checks are only omitted when it's absolutely necessary to get the best possible performance (for example in production builds) |
Out-of-bounds access is still UB – so we can have checking in debug builds. I think checking accesses, not variables, is the right choice, because it is accesses that take time, not variables. Eliding bounds checking which we can prove we don't need is unrelated. |
"Out-of-bounds access is still UB – so we can have checking in debug builds." "Eliding bounds checking which we can prove we don't need is unrelated."
|
See #423 for handling debugging and OOB indexing |
The Rust team has spent some time discussing this issue (and related RFCs) and, while we agree that the ergonomics of unsafe programming can and should improve over time, we're not ready to commit to a design yet. For the 1.0 release, we plan to work on shoring up the existing library APIs for unsafe code, making them coherent and ergonomic, but are not ready to make language-level commitments. I'm going to close this as Postponed for now, and create a tracking issue in this repo so that we can revisit it later on. Thanks for the effort in writing this up! |
Tracking issue here. |
Deprecate setupComponentManager String Lookup
Rendered