Hacker News new | past | comments | ask | show | jobs | submit login

Here's a weird bit of C++ code:

    std::vector<int> foo = ...;
    for (iter = foo.begin(); iter != foo.end(); ++iter) {
        *iter += 1;
        foo[0] += *iter;
    }
A naive translation to Rust would run afoul of the borrow checker. If we are to compile C++ to Rust, how are we to address this? Should we simply generate unsafe Rust?



I don't think the point is to translate existing languages to rust, though ten author seems to have tried it with success with js (and maybe others?).

The point is to translate new, generally higher level languages to rust. These won't fall afoul of the borrow checker if handled right. I suspect Rust will not have a major restricting effect on their design either. A lot of the borrow checker issues can be papered over with sufficient codegen. When I was working on rust-gc, I often remarked that if we wrote some codegen plugins that convert all types to `Gc<RefCell<Trait>>`, we're effectively Java ;) (Most of this papering over can be done in a way that avoids runtime cost, though not this specific simplistic example for Java)

Though there is this thing called Corrode which is trying to translate C into Rust and iirc they are trying to minimize the unsafe usage in the generated code. That may eventually be able to convert your code into an indexed iteration.

(Also, while the specific code you wrote is safe, if the mutation being performed were more complicated with a non-primitive, it could lead to segfaults)


What do you consider weird about this code? This is fairly straightforward C++, as far as syntax and language use. Do you mean the logic of what it is doing seems weird to you?


Yes, it's just accomplishing something weird. And it's straightforward C++ (as you say) that uses multiple mutable references to a single object. Of course Rust forbids this.

How would one transpile this code to Rust? Or is the idea that every language ought to have Rust-style semantics? If so, it's hard to reconcile that with "Rust is the new LLVM," since LLVM does not demand such strict semantics.


    let mut foo = vec!(1, 2, 3);
    for mut iter in 0..foo.len() {
        iter += 1;
        foo[0] += iter;
    }
No unsafe required, I think.


Points for cleverness - you replaced the iterator with a call to len(). Array indices are sometimes thought of as back-door references, because they let you cheat in this manner.

But you didn't quite get away with it. The C++ code increments the value in the array through the iterator, while the above Rust increments the iterator (err, index) itself. So it doesn't produce the same result.

More generally, std::vector in C++ typically implements iterators with just raw pointers. This is quite unsafe, but doesn't impose borrow-checker restrictions on the user. How can such languages be compiled to Rust?


>But you didn't quite get away with it. The C++ code increments the value in the array through the iterator, while the above Rust increments the iterator (err, index) itself. So it doesn't produce the same result.

I have to admit that I don't understand you here. Can you elaborate on the difference between the two programs?


Sure. The C++:

    for (iter = foo.begin(); iter != foo.end(); ++iter) {
        *iter += 1;
        ...
Here `iter` is a pointer. We dereference it and increment the result. This adds 1 to every value in the `foo` vector.

The Rust:

   for mut iter in 0..foo.len() {
        iter += 1;
        ...
Here `iter` is an array index, and `iter += 1` increments that index. The contents of the `foo` vector are not modified.


>Here `iter` is a pointer. We dereference it and increment the result. This adds 1 to every value in the `foo` vector.

Huh? How is `iter` related to foo? When you go `iter += 1` aren't you just incrementing the variable behind iter?

Unless... OK, I'm not very familiar with C or C++, totally newb here. Are you doing pointer arithmetics here, when you increment iter?

But wait, if you are doing pointer arithmetic, I thought you are not supposed to deref the pointer. Otherwise you are doing arithmetics on the variable behind the pointer, not the pointer itself.

Also, with `foo[0] += *iter`, you are just adding iter to the first element of foo, again and again. You are not actually iterating on foo.

I'm confused...


Think of `foo.begin()` as returning a pointer to the first element of the array, or something which acts like it (similar to Rust's `Deref`). Think of `foo.end()` as returning a pointer to a "virtual" element after the last element of the array. Then `++iter` increments this pointer, so it points to the next element of `foo`.

Since `iter` points to an element of `foo`, `* iter` is the element itself. `* iter += 1` adds one to that element. `foo[0] += * iter` takes the current element, adds it to the first element of `foo`, and stores the result as the first element of `foo`.

The catch is that, in Rust terms, you have a mutable reference to an arbitrary element of `foo` (the reference being `iter`), and you are getting a mutable reference to another element of `foo` (the reference being `foo[0]`) at the same time. Even worse, in the first iteration, `iter` points to the first element, so `iter` and `foo[0]` are two mutable references to the same location, which exist at the same time! You can't do that in Rust with references (you can with pointers and `unsafe`, and in this case it happens that there is no data race, but...)

The correct safe Rust translation would be:

    let mut foo = vec!(1, 2, 3);
    for index in 0..foo.len() {
        foo[index] += 1;
        foo[0] += foo[index];
    }
But, for more complicated cases, an equivalent translation won't be as obvious.


What's the advantage of doing pointer arithmetics as opposed to incrementing index variable, like I did?


The advantage of iterators is the abstraction. Collections like maps and sets don't even have indexes.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: