47 comments

[ 5.6 ms ] story [ 105 ms ] thread
The backstory here is that Alexis is a community member who came to Rust with a strong interest in improving the quality of the data structures in our stdlib, many of which had deteriorated significantly over the years through neglect and language upheaval. He began by compiling an enormous list of general cleanups[1] which eventually sparked an RFC for wide-scale collections reform[2] which was accepted and has begun work in earnest.[3] It's an enormously impressive effort that has engaged tons of new contributors, and goes to show what even a single driven community member can achieve.

[1] https://github.com/rust-lang/rust/issues/18009

[2] https://github.com/rust-lang/rfcs/pull/235

[3] https://github.com/rust-lang/rust/issues/18424

I am trying to understand how these two statements work together.

1) > unsafe code is infectious like Java's exceptions. If you call an unsafe function, you need to mark yourself as unsafe, or explicitly state the boundaries where the unsafety is handled.

2) > Unsafe is for the lowest levels of abstraction to deal with

So according to 1) unsafe code is infectious but according to 2) you should only use it at the lowest level in your code. By this I understand things like mmap-ing a file, get a value out of shared memory, interacting with C code and so on. Presumably if your high level code depends on low level code, wouldn't the lower level "usafety" bubble all the way to the top. And next thing your main do_top_level_business_logic() function has to be wrapped in an unsafe block?

Now while typing this I am thinking of maybe one way to handle is and that is to create a separate task and shuffle data via messages between another unsafe task. If task crashes just restart it...

It bubbles up as far as an `unsafe { ... }` block, but no higher.
Yes, presuming that the author of the unsafe code in question has manually verified the effective safety in all relevant scenarios. `unsafe` is an assertion to the Rust compiler that you know what you're doing, and you will preserve its guarantees as far as external observers are concerned.

I might point out to the grandparent post that even the basic vector collection in Rust is full of unsafe {} blocks.

I understand now, thanks for explaining. It was implied in the article too, by analogy with java's exceptions, I just forgot how that works too.
To clarify, there are two places that you can use the `unsafe` keyword:

  unsafe { /* arbitrary code in here */ }  // applied to a block

  unsafe fn foo() { /* function body */ }  // applied to a function
It's the second form that is infectious. When applied to a function, that function can only be called from an unsafe block or another unsafe function. The use of this form is to say, "hey, as the author of this function, I have no ability to determine whether or not this function is safe to use; let the caller beware."

However if, as the author of a function, you can take steps to ensure that the function is used safely, then you can stop the infection with an unsafe block.

For illustration, let's do one of the things mentioned in the post. Here's a function that prints every element in a vector:

  fn foo1(v: Vec<int>) {
      let length = v.len();
      let mut i = 0;
      while i < length {
          println!("{}", v[i]);
          i += 1;
      }
  }
That `v[i]` in there is potentially doing bounds checking (in practice a simple case like this would get optimized away by LLVM, but let's ignore that), but note that this is superfluous because we've written this code such that `i` will always be a valid index. So let's turn off bounds checking by using the `unsafe_get` method mentioned in the OP:

  fn foo2(v: Vec<int>) {
      let length = v.len();
      let mut i = 0;
      while i < length {
          println!("{}", unsafe { v.unsafe_get(i) });
          i += 1;
      }
  }
Like I was saying above, `unsafe_get` is a function that is itself marked unsafe, because the author of `unsafe_get` knows that you can cause memory unsafety by passing in an invalid index. But because we've written this program carefully, we know that both foo1 and foo2 are exactly as safe, so we've fenced off the unsafety here with the `unsafe` block. Both foo1 and foo2 can be called from safe Rust code with no special consideration.

Now, what happens when, years down the road, someone who isn't paying attention accidentally modifies this function?

  fn foo3(v: Vec<int>) {
      let length = v.len();
      let mut i = 0;
      while i <= length {  // look carefully
          println!("{}", unsafe { v.unsafe_get(i) });
          i += 1;
      }
  }
This version of the function contains an off-by-one error. Anyone calling foo3 will try to index beyond the end of the array, and all hell will break loose. What do you do then? Well, you grep for the word "unsafe" and you start auditing unsafe blocks, looking at all the inputs that you send into them. This is generally the same approach you'd take in any other systems language, except that by limiting where unsafe code can appear you reduce the code that can potentially go wrong by 10-100 times, and speed up your auditing commeasurably. And the promise made in the OP stands: if you don't yourself use `unsafe` code and memory safety happens, it's someone else's fault, not yours. :)

And how often do you need to resort to unsafe code in Rust? It depends on your domain. Efficient data structures often require all sorts of custom pointer tricks, so maybe a tenth to a third of their code is unsafe in Rust. For application-level programming, I'd expect no more than 1-3% unsafe code (maybe up to 5-10% if you're trying to push performance to the absolute limit, like Servo is doing).

Anyone calling foo3 will try to index beyond the end of the array, and all hell will break loose. What do you do then? Well, you grep for the word "unsafe" and you start auditing unsafe blocks, looking at all the inputs that you send into them.

That's not a very good solution. "Kill problems, don't just wound then" - Kelly Johnson, Lockheed Skunk Works, designer of the U-2 and SR-71.

I used to work on this problem, about a decade ago. I was trying to figure out how to make C++ safe. I finally gave up, since memory safety wasn't a priority with the C++ committee. I'd proposed scope-based "borrowing" (I used the term "keeping", as the inverse of borrowing) and reference counts.

Complex data structures remained a problem. The best idea I could come up with was to use weak pointers heavily. When you have something like a GUI widget library, with pointers going upwards, downwards, and sideways, who owns what is a big issue. One solution is that all the graphics widgets are owned by some top level object, like the "window" object, all the inter-widget links are weak, and all the weak pointers are in objects that can't outlive the top level object. This creates a sort of allocation pool. Objects pointed to by weak pointers can't fully go away until the parent object of all the weak pointers goes away. They only get marked as invalid for weak pointer referencing.

This is memory-safe, but you don't get the space used by widgets back until the top-level page object is deleted. That can be a problem for long-lived graph structures with a lot of churn, like the scene graph in a game or the active collision structures of a collision detection system. For most applications, though, it's not a big deal; browsers build a DOM, use a DOM, and discard a DOM when the page closes. Might be a problem if you left Facebook open for a month.

Go for safety. We do not need yet another unsafe language. We have plenty of those. The day there's a CERT advisory for a buffer overflow in a Rust program, Rust has failed.

Fortunately for your use case, Rust provides a reference-counted type in the standard library that does indeed feature weak pointers to break cycles. It's like C++'s `shared_ptr`, except safer and faster (since Rust's safety rules mean that we can prove that the refcounted data isn't shared between threads, and hence we don't need to resort to atomic operations). All of these are possible because of a thin layer of `unsafe` code on the ground, which we then wrap in a safe interface.

  > We do not need yet another unsafe language. We have plenty of those.
I sadly must disagree. If safety were truly paramount, people would have dropped C++ entirely in favor of Java in the 90s. The people still using C++ aren't idiots, they just have requirements with respect to efficiency, control, determinism, and expressiveness that other languages don't match. Rust exists to provide an alternative to these people, so that we can at last bring some semblance of safety to domains with the requirements intimated above.

  > The day there's a CERT advisory for a buffer overflow in a Rust program,
  > Rust has failed.
I dread that day, and yet I know that it's inevitable. In the meantime, that same buffer-overflowing Rust program may well have produced ten times the CERT advisories had it been written in C. I'm not an absolutist; I'll take that improvement as a win.

  > I used to work on this problem, about a decade ago.
  > I was trying to figure out how to make C++ safe.
Honestly, the Rust developers would love your perspective if you'd like to take a harder look at Rust.
Well, one key issue is that the compiler needs to know that program-terminating fault checks are special for optimization purposes. It's OK to terminate a program early. If a loop, when entered, will inevitably subscript out of range at iteration 101, then it's OK to detect that at loop entry, and never execute iterations 0..100. Even if there's a print statement in the loop.

Normal compiler hoisting operations aren't allowed to do that. It violates normal execution order semantics. If you don't do that, you can't optimize checks fully. This is why generating assert statements in macros or templates and then trying to optimize them doesn't work. Asserts may need to be hoisted through statements with side effects. Compilers have to handle that at the flow graph level.

This works best if you're not allowed to capture such errors. If you're headed for an unavoidable crash, it's best to have it sooner, rather than later. Go takes a hard line on this - no exception mechanism. (Admittedly some people are abusing Go's "panic/recover".) Google's position is that in their kind of operation, it's better to fail the whole program. They can recover from a failed server, after all. Erlang also take a "fail early" position.

As for the need to eliminate errors, the recent history of computer language development has been in the direction of increased safety. All the major scripting languages are memory-safe. Newer hard-compiled languages such as D and Go are memory-safe, although they have to use GC to do it. A whole generation of programmers have never had to look for a buffer overflow or a dangling pointer.

Meanwhile, in the security world, the buffer overflow threat has become worse. It's not script kiddies any more. It's national intelligence agencies (ours, theirs, who knows who else?) with big budgets and competent people looking for exploits. There's a black market in zero-day exploits now. Rust has a fighting chance to fix that. Not just trim it back a little. Fix it. Please don't blow this chance.

Latest buffer overflow reported by CERT: November 14, 2014:

https://www.us-cert.gov/ncas/alerts/TA14-318B

The Microsoft Windows OLE OleAut32.dll library provides the SafeArrayRedim function that allows resizing of SAFEARRAY objects in memory.... In certain circumstances, this library does not properly check sizes of arrays when an error occurs. The improper size allows an attacker to manipulate memory in a way that can bypass the Internet Explorer Enhanced Protected Mode (EPM) sandbox as well as the Enhanced Mitigation Experience Toolkit (EMET).

This vulnerability can be exploited using a specially-crafted web page utilizing VBscript in Internet Explorer. However, it may impact other software that makes use of OleAut32.dll and VBscript. Exploit code is publicly available for this vulnerability. Arbitrary code can be run on the computer with user privileges.

That's why "carefully checked" "unsafe" code doesn't work. "SafeArrayRedim" - wasn't.

> All the major scripting languages are memory-safe. Newer hard-compiled languages such as D and Go are memory-safe, although they have to use GC to do it. A whole generation of programmers have never had to look for a buffer overflow or a dangling pointer.

They all have the ability to write unsafe code in the same manner as Rust; but only Rust has it really built-in to the language so the compiler can reason about it. E.g.

- http://golang.org/pkg/unsafe/

- https://docs.python.org/3.4/library/ctypes.html

It just doesn't make sense to presuppose that it is any worse in Rust. In fact, quite the opposite, the type system is designed for ensuring memory safety without a GC, and in multithreaded environments. I believe this is not true for Go (it is vulnerable to data races, and requires a runtime tool to detect some subset of the ones can occur in any given program), but I don't know about D.

Rust cannot remove `unsafe` without being useless for its target, and useless as a language (it wouldn't even have FFI).

> Rust cannot remove `unsafe` without being useless for its target, and useless as a language (it wouldn't even have FFI).

I like to think of Haskell as a parallel: It guarantees side effect-free code. But you still need an escape hatch in the form of unsafePerformIO in order to use the FFI. The language's semantics can easily be broken with that functionality, but it seems to be a necessary price to pay if you want to have any kind of FFI. (<- Well, I guess something like Proof Carrying Code could be an alternative..)

Calling external code is a separate issue. This is about unsafe code within the language.
I thought it was about buffer overflows and security vulnerabilities within Rust programs? i.e. not just the parts written directly in Rust. Looking at anything else seems a bit silly, since the thing that runs in the real world is the whole program.

The low-level code has to exist somewhere, vectors need to be allocated, strings need to be printed, and people will want the performance. This code is either going to be in external libs called via FFI, as raw codegen in the compiler or in Rust. The last sounds like the one that is safest since the FFI code would have to be written in one of the unsafe systems languages, while writing in Rust gives all the type system advantages and high-level features (references, pattern matching), not to mention avoiding the (mental) overhead of correctly interacting between languages.

It seems to me that taking away the low-level power of opt-in `unsafe` is just myopically punting the problem along, rather than seeing it as giving Rust the ability to avoid having to write really unsafe C plugins (like Python etc.).

But they aren't separate issues. If you couldn't do these things in Rust, people would just drop into C to do them. Given that that would introduce much more unsafety into Rust programs, Rust does the right thing by reifying unsafe memory operations and sequestering them away.
I'd like to reassure you that the Rust community absolutely does not condone wanton usage of `unsafe` unless profiling indicates an area that really requires low-level trickery. The discussion in the article linked here concerns the standard library, which needs to be fast because otherwise people will reimplement it themselves, likely poorly, over and over again. By accepting potential unsafety here we are saving others the effort of a half-baked, insecure collections library while also concentrating our audits on well-known and oft-used code.

  > All the major scripting languages are memory-safe.
  > Newer hard-compiled languages such as D and Go are memory-safe
Rust's definition of safety actually surpasses most of these languages, as it extends to safety in the face of concurrent operations. Rust statically prevents data races, which AFAIK is a property that until now has only been exhibited by thoroughly immutable languages such as Erlang. That it manages all this without a GC is very exciting.

  > Meanwhile, in the security world,
  > the buffer overflow threat has become worse.
The Rust developers agree with you. Rust is motivated by the fact that Mozilla is tired of Firefox getting pwned by dint of C++'s looseness, and despairing at the fact that further improvements to Firefox will entail tacking concurrency onto a multi-million-line C++ codebase dating back to 1997, which would be notoriously difficult even if security weren't a concern. They're writing a brand-new browser engine in Servo with a focus on massive concurrency and user safety.

I guarantee you that Rust cares a great deal about safety. It goes to lengths that almost no other language is willing to go in pursuit of this goal. However, it is also a thoroughly pragmatic language. We won't make the world a better place by blindly preaching dogma. Rust also won't be the last language ever made. If nothing else, even baby steps toward a safer world are a measure of progress. :)

Rust has to have some equivalent to `unsafe`; it is designed to be used for low-level code, and opt-in unsafety is far better than opt-out. Anything like FFI is outside the realms of what a compiler can possibly verify, since it knows nothing about what the arbitrary C functions do, or what pre-/post-conditions hold. Furthermore, all the normal tools provided by Rust are available in unsafe blocks.

The low-level code has to be written somewhere (Rust needs to have a vector type, needs to have an efficient hashmap), and frankly, writing it in Rust is better than writing it in C and using FFI to import it.

Rust is going for safety.

> Go for safety. We do not need yet another unsafe language. We have plenty of those.

Do we? The only ones I hear about people using are C/++. On the other hand, safe systems languages seem to end up being forgotten, as people opt for C/++ instead for systems/performance domains. Presumably in part because they use things like garbage collection, giving up some of the more raw control and probably also speed that C/++ provides.

It really seems that any language that wishes to challenge C/++ on their own turf needs to offer the power of C/++ as far as it is possible. In Rust's case, that is possible as long as you're being explicit about where the safe interfaces end and the unsafe ones begin.

But, with a little sprinkle of unsafe inside this method, we can make it happen.

Uh oh. If you have to turn off subscript checking in Rust for "performance" for simple loop iteration, something is very wrong.

The way this ought to work is by the compiler hoisting checks out of the loop. Consider

let mut v = vec![1i32, 2, 3, 4, 5];

let vlen = v.len()

for i in range(0, vlen) {

    let x = &mut v[i];

    // do some work with x
}

(Have to double space due to lack of formatting capabilities here.)

Subscript checking requires something comparable to

"assert(i >= 0 && i < v.len())" at the subscript check v[i]

The compiler, knowing how "for" works, can hoist that check out of the loop, so it becomes

"assert(0 >= 0 && vlen-1 < v.len())"

at the the top of the loop, before the FOR statement is entered. Now the check only has to be executed once. This optimization is valid for all iterative FOR loops where the iteration variable and the array are not modified within the loop, and the compiler has to check that.

Then, after hoisting, some algebraic simplification can be applied to the expression in the assert. This requires a small theorem prover, or more cheaply, some rewrite rules for the common cases.

"assert(true && vlen <= v.len())" "assert(vlen <= v.len())"

Now, flow analysis shows that, at the point of the assert, vlen = v.len(). So we get

"assert(v.len() <= v.len())"

and finally

"assert(true)"

which is eliminated as dead code. Zero overhead for subscript checking, yet proved correct.

Now that's how it ought to work. One of the Go compilers does this for simple loops like this. Sprinkling "unsafe" around, and making people use funny library constructs for simple loops, is doing it wrong.

This seems to be a forgotten technology. There were Pascal compilers in the 1980s which did this. In practice, about 95% of subscript checks could be optimized out for Pascal. You can almost always get checks out of iterative inner loops, which is where they really matter.

> (Have to double space due to lack of formatting capabilities here.)

Indent by four spaces to get a <pre> tag:

    let mut v = vec![1i32, 2, 3, 4, 5];
    let vlen = v.len()
    for i in range(0, vlen) {
        let x = &mut v[i];

        // do some work with x
    }
> This seems to be a forgotten technology.

Given what I know about Rust, I strongly suspect that there is a way in Rust to iterate over a vector with only a single hoisted bounds-check. Hopefully other Rust people can chime in on what this is.

In most cases, you use iterators rather than indexing

    for x in v.iter_mut() {
        // do some work with x
    }
or if you need the index

    for (i, x) in v.iter_mut().enumerate() {
        // do some work with x and i
    }
similar to Python's enumerate(). These loops never need to do any bounds-checking, even when compiling without optimization.

In this example the iteration count is a small compile-time constant (5), and the loop body is small, so when compiling with optimizations the loop will be unrolled.

> I strongly suspect that there is a way in Rust to iterate over a vector with only a single hoisted bounds-check. Hopefully other Rust people can chime in on what this is.

Iterators.

If we take your code and modify it so that the compiler can't just get rid of the array acces, we get:

    extern crate test;

    fn main() {
        let mut v = vec![1i32, 2, 3, 4, 5];
        let vlen = v.len();
        for i in range(0, vlen) {
            let x = &mut v[i];
            test::black_box(x);  // do some work with x
        }
    }
Compiling this file with rustc -O, the compiler not only gets rid of the bounds checking, it gets rid of the loop altogether by unrolling it completely.

LLVM IR: https://gist.github.com/veddan/1535a8718cf2c85006ea

Thanks. Can you do that again for a case where the compiler can't see the number of iterations at compile time?
Let's change the example to

    pub fn f(v: &mut [u8]) {
        let vlen = v.len();
        for i in range(0, vlen) {
            let x = &mut v[i];
            test::black_box(x);  // do some work with x
        }
    }
The output assembly of latest nightly of rustc is:

    f::h1371efe4e4343230faa:
        cmp rsp, qword ptr fs:[112]
        ja  .LBB0_2
        movabs  r10, 16
        movabs  r11, 0
        call    __morestack
        ret
    .LBB0_2:
        push    rbp
        mov rbp, rsp
        push    rax
        mov rax, qword ptr [rdi + 8]
        test    rax, rax
        je  .LBB0_5
        mov rcx, qword ptr [rdi]
        xor edx, edx
        lea rsi, qword ptr [rbp - 8]
    .LBB0_4:
        lea rdi, qword ptr [rcx + rdx]
        inc rdx
        mov qword ptr [rbp - 8], rdi
        cmp rdx, rax
        jb  .LBB0_4
    .LBB0_5:
        add rsp, 8
        pop rbp
        ret
Thanks. I don't see a call to test::black_box(x), so I assume that was an empty function optimized out.

Would you change "let vlen = v.len()" to "let vlen = v.len() + 1" and try again, please? That introduces an off-by-one error and a buffer overflow. Let's see what the compiler does with it. Thanks.

Your modification results in the inclusion of a bounds check in the inner loop:

    .LBB0_4:
    	cmp	rdx, rsi
    	jae	.LBB0_7
At `LBB0_7` is a call to `panic_bounds_check`.
> Thanks. I don't see a call to test::black_box(x), so I assume that was an empty function optimized out.

black_box is and does nothing, its purpose is to be opaque to the optimiser and make values it is passed "used" for optimiser purposes, otherwise the optimiser would remove the whole thing since it's a noop.

(Replying to message below, indent limit reached).

The panic check is inside the loop, not hoisted out of the loop?

OK, that means it's only optimizing bounds tests out when it can, not hoisting them out of loops. That's not bad for a compiler in the early stages. Is a matrix multiply free of checks in the inner loops? That's a good test, because most numerical code has a lot of matrix multiplies underneath. A useful goal is to have most of the inner loops from algorithms in Numerical Recipes free of unnecessary subscript checks.

If that can be done, is the "unsafe" stuff necessary at all?

At the limit, `unsafe` blocks would still be necessary for any FFI used to call into C code, which is trivially proven unsafe.

It's mistaken to think of `unsafe` as a novel language feature. All other languages merely defer their unsafe shenanigans to native C calls. I can just as easily write a memory-unsafe program in Python. In Rust, we use `unsafe` to hoist logic out of C wherever possible, to make our code even more safe; even the unsafe dialect of Rust is safer than C.

(comment deleted)
To elaborate, when Rust says that array access is bounds-checked while iterators aren't, what it means is that iterators are guaranteed not to bounds check, while typical array accesses may or may not be optimized away. With the GP's approach, not only do you need to hardcode the hoisting optimization into the frontend, you need the body of the loop to follow a specific patterns and you need to trust that the compiler will recognize the cases where the optimization can be applied. By avoiding privileged optimizations in the frontend and by giving programmers low-level tools where they need them, Rust empowers library authors to get things done without having to bug the Rust developers themselves to implement them.
That's kind of what compilers are for. One of their jobs is static analysis. They have the graphs needed for hoisting. Libraries don't.

This may be a problem with the existing compiler, if the Rust implementation is mostly a front-end to LLVM. I'd hate to see "unsafe" code baked into the language standard, though. C++ tried to fix the mess underneath with template libraries. That didn't end well.

"Giving programmers low-level tools when they need them" as an excuse for abandoning language safety is a recipe for bad code. There's a long, long history of that not working. "Unsafe" code should be very, very rare, used for dealing with device registers and such.

This sounds like designing buffer overflows into Rust.

(comment deleted)
Checked array access is implemented with unsafe code.

Actual code implementing the `[]` operator for arrays ("slices"):

    fn index(&self, &index: &uint) -> &T {
        assert!(index < self.len());

        unsafe { mem::transmute(self.repr().data.offset(index as int)) }
    }
By having `unsafe {}` it's possible to build low-level functionality as libraries rather than in the compiler. There no reason to think that code such as this would be less prone to bugs if it were hard-coded into the compiler. I'd be more inclined to believe the opposite.
> That's kind of what compilers are for.

To be black boxes? Yes, they seem to have a long history of being that.

If I want to implement a stack data type being backed by an array, and I can not be sure that bounds checking is optimized away, I want to be able to turn off bounds checking if I'm 100% sure that my implementation indexes into the array in a correct way. I have all the information that I need about indexing into the array, since I don't expose indexing to whoever is using my stack. The compiler doesn't know more than me, in that sense. So why would I need compiler support - as in some graph which is a result of static analysis - in order to eliminate bounds checking?

I want to be able to turn off bounds checking if I'm 100% sure

But is everyone else 100% sure? Look at that CERT advisory I posted from Microsoft. "SafeBufferResize" - wasn't. Somebody at Microsoft was probably "100% sure".

If a buffer overflow in your code meant being fired, would you turn off subscript checking?

> If a buffer overflow in your code meant being fired, would you turn off subscript checking?

If I could get fired for my implementation not being efficient enough (and the performance hit from the bounds checking mattered as shown through benchmarking, yadda yadda), then yes. :)

> "Giving programmers low-level tools when they need them" as an excuse for abandoning language safety is a recipe for bad code. There's a long, long history of that not working. "Unsafe" code should be very, very rare, used for dealing with device registers and such.

How does Rust get an efficient vector type? How does it spawn a thread? How does it print something, or write something to file?

All of these need really low-level code e.g. for interacting with the operating system APIs, or handling possibly-uninitialised data.

Rust explicitly segregates `unsafe`, and we try to instil a strong aversion to using it exactly because it is dangerous and very easy to get wrong, but it's just not reasonable to remove that ability.

> By avoiding privileged optimizations in the frontend and by giving programmers low-level tools where they need them, Rust empowers library authors to get things done without having to bug the Rust developers themselves to implement them.

And there are cases where it is desirable to use unsafe indexing (beyond implementing the standard library iterators). While LLVM is able to get rid of nounds-checking for a simple indexing loop, it's less likely to do in other circumstances. For example, a high-performance sorting function might want to use (some) unsafe indexing. The access patterns and loop exit conditions can be very complex, and LLVM will not be clever enough to reliable optimize out bounds checks.

You don't have to turn off subscript checking for simple loop iteration. As the article says, you're supposed to use .iter_mut() for this purpose where the unsafe code is isolated to library code.

The passage you quoted is about splitting a slice in two, not loop iteration. Bounds-check elimination can't solve this problem, and once again it's about unsafe code encapsulated in libraries. The function split_at_mut is perfectly safe to use in safe code.

Rust uses an industrial strength optimiser (LLVM) and the analysis you list is all rather standard. As others have said, one usually doesn't need manual indexing in loops, and even if it is required, an obvious loop like that is quite transparent to the compiler.

In any case, it's an implementation detail of the compiler, not a language thing.

This is long with a lot of detail, but to me the key bit of information is: iterators (as expressed by Rust's Iterator trait) have some noticeable drawbacks. Notably, you can't delete while iterating.

However, a different abstraction called Cursors can accommodate this. I asked recently: "does this mean iterators are broken, or are iterators vs. cursors more of a fundamental tradeoff?" I got this very helpful answer in reply: http://www.reddit.com/r/rust/comments/2mkra1/impressions_aft...

After reading this bit:

  // Vec is our growable heap-allocated array type.
  // This is just a handy way to make one with some fixed data.
  let mut v = vec![1i32, 2, 3, 4, 5];
  let x = v.get(2);
  v.push(6); // oops, sorry x!
I really hope that the Rust compiler has some really good error messages explaining what the problem is because I couldn't see what was wrong with this fragment until I read the explanation.

Saying that, after reading the explanation it became a bit more obvious, and I really like the way of describing the relationship to the pointer as a 'view' and 'loan', which as a c++ programmer (though clearly spending too much time with GC languages) I can understand.

> I really hope that the Rust compiler has some really good error messages explaining what the problem is because I couldn't see what was wrong with this fragment until I read the explanation.

They're not always perfect, but in this case it's quite good.

    err.rs:4:9: 4:10 error: cannot borrow `v` as mutable because it is also borrowed as immutable
    err.rs:4         v.push(6); // oops, sorry x!
                     ^
    err.rs:3:17: 3:18 note: previous borrow of `v` occurs here; the immutable borrow prevents subsequent moves or mutable borrows of `v` until the borrow ends
    err.rs:3         let x = v.get(2);
                             ^
    err.rs:5:6: 5:6 note: previous borrow ends here
    err.rs:1     fn main() {
    err.rs:2         let mut v = vec![1i32, 2, 3, 4, 5];
    err.rs:3         let x = v.get(2);
    err.rs:4         v.push(6); // oops, sorry x!
    err.rs:5     }
                 ^
If you want to try out code examples, the Rust playpen is nice: http://play.rust-lang.org
I'm quite surprised by Rust behaviour here: if x isn't used after 'v.push(6);' (doesn't seem too difficult to know by the compiler) then I would have expected the compiler to allow this code and to reject it only if x is used after the 'v.push(6)'.. Sure as he explained you can do "let mut v = vec![1i32, 2, 3, 4, 5]; { let x = v.get(2); <work with x> } v.push(6);" but this must be quite annoying..
I think this might be more difficult than it seems, given that Rust allows you to store references in structs. I'll ask the developers sometime.
Currently, borrows last for block scope, which is why this doesn't work. There is a feature in the making called SEME (single entry, multiple exit) which will resolve this and other annoying borrow hazards. It at least at one point was considered a priority for 1.0.