Rust for C++ programmers – part 2: control flow
featherweightmusings.blogspot.com
featherweightmusings.blogspot.com
for i in range(0, all.len()) {
println!("{}: {}", i, all.get(i));
}
I'd suggest using `enumerate`[0] instead of creating a range on an existing collection: for (i, n) in all.iter().enumerate() {
println!("{}: {}", i, n);
}
[0] http://static.rust-lang.org/doc/0.10/std/iter/struct.Enumera...By the way, I always wonder: is there a reason for no Iterable trait, which for could just take directly and implicitly call `.iter()` on? E.g. why
for e in my_vec.iter() {
but not for e in my_vec {
?Iterator implements Iterable as a trivial self-return (by default), and `for` only and always takes an Iterable?
> what if you implement both?
Depends what sort of weirdness you're trying to do I guess.
> I believe it didn't work with the trait matching rules.
Ah. Too bad.
One of the reasons why the C++ for (:) syntax above feels like such an improvement over the traditional iterator for loop is that it cuts out a lot of noise that's the same for almost every container loop you ever do. The same objection could be raised as in another branch of this conversation, that you might have other kinds of ranges on an object, and I've even done that before (as obj.something_begin() and obj.something_end()). But even before ranged-for came it almost always turned out ugly and I regretted it.
If most uses of for in Rust call iter() (I don't know that they do, my experience with it thus far is really minimal, but it does seem common so far), it seems like a case where use should lead design and the extra noise cut out.
Basically, if I have more than one kind of range on an object I'd probably rather have functions on the object that return the 'unusual' ranges for the iteration. So rather than having:
for i in obj.iter() # the normal thing that I use all the time
for i in obj.bfs_iter() # something I use sometimes to be more explicit
for i in obj.dfs_iter() # another thing I use sometimes
I'd rather have: for i in obj # the normal thing that I use all the time
for i in obj.breadth_first() # something I use sometimes to be more explicit
for i in obj.depth_first() # another thing I use sometimes
Or if there is no sensible default, to be forced to name the thing I actually need.It seems entirely sensible to me to expect the for loop construct to take an Iterable object. It actually makes more sense to me than for it to take an iterator directly, conceptually.
On the question of 'what if both?' I'd propose making it so hard (or impossible?) to do that. If for took Iterable, and Iterators also implemented Iterable as "return this", there'd really be no concept of having both (except in that if you have an iterator, it is always both) and for itself would be left fairly uncomplicated while still being able to take both Iterable and Iterator objects.
I'm probably missing some reason why this isn't an obvious way to go, as a definite Rust newb, but it would feel natural to (my C++-rotted perhaps) brain. Also I know there's no language changes until post-1.0, so for now it is what it is.
[edit] Huh. Ok, re-reading it looks like this was almost all covered ground. Never mind.
Apparently is the language clever enough to match the types (which is good), why isn't than simply clever enough to recognize then that in on the array needs the iteration? Why isn't possible to directly enumerate the array?
Even (at least this one) C++ has something simple:
http://msdn.microsoft.com/en-us/library/jj203382.aspx
for( auto y : x ) { // Copy of 'x', almost always undesirable
cout << y << " ";
}
for( auto &y : x ) { // Type inference by reference.
// Observes and/or modifies in-place. Preferred when modify is needed.
cout << y << " ";
}
for( const auto &y : x ) { // Type inference by reference.
// Observes in-place. Preferred when no modify is needed.
Not even "in" keyword is used there but it still allows iterations through anything that can be iterated without writing too much red tape, by invisibly expanding to these famous STL declare iterator with the unnecessary long declaration, iterate between begin and end constructs: range-based for:
Automatically recognizes arrays.
Recognizes containers that have .begin() and .end().
Uses argument-dependent lookup begin() and end() for anything elseenumerate is a method of the Iterator trait, so you need an iterator. This way it can have a truckload of reusable methods without polluting every collection's namespace I guess, and still things work for collections with multiple iterators (just ask for the right one).
The iteration method does not have to be called ``iter`` I'd think (e.g. a tree structure might provide both a depth-first and a breadth-first iterator, none of which would be accessed via `iter`?)
for x in all does .iter()
for (x,y) in all.enumerate() does .iter.enumerate()
And were there's no one iter() like trees, only by them it would have to be specified, as they wouldn't have iter(): for x in all.depthfirstiter()
for (x,y) in all.depthfirstiter().enumerate()
95% of times 5% percent of the constructs are used, why not making them pleasant?See pcwalton's answer in an other subthread, an Iterable trait (which for would use instead of Iterator) was tried and didn't work out previously, but it's not shelved.
> for (x,y) in all.enumerate() does .iter.enumerate()
That makes literally no sense. What if enumerate() is the thing returning an iterable because reasons? Now it calls enumerate() on the wrong object, or blows up because there's no .iter in the first place.
You could have 30 methods on the Iterable trait (or directly on your vector) which would just delegate to the corresponding Iterator. Not sure there's that much value in it.
> for x in all.depthfirstiter()
Except according to your previous declaration that would result in a call to `all.iter.depthfirstiter()`. Or the compiler has to go magic things around, traverse the chain until it gets the first (or last? Or all of them? How do you pick which one's right?) Iterable, an inject an iter() calls in it…
But again, it's just a matter of taste and not really worth wasting a lot of time worrying about. The important thing about Rust is its semantics (speed, safety, control), not its syntax.
Agreed. I love Haskell syntax above all else, but I like Rust's semantics too much to let that get in the way of me using it.
function fibunacci(integer index) {
if (index <= 1) {
return 1;
} else {
return fibunacci(index-1) + fibunacci(index-2);
}
}
compared to: fn fib(int n) {
if (n <= 1) {
return 1;
} else {
return fib(n-1) + fib(n-2);
}
}
Personally, I prefer `fn`, `int`, and `str`. I actually think that the short names are a nice way of distinguishing between built-in keywords (abbreviated, like `str`), and user-defined names (typically whole words). Also, short names are useful since they can be used as pre/post-fixes to function names like `strsplit`.also shouldn't the returns be unnecessary here since the conditional is an expression?
int -> int (ok)
str -> string
but anyway.. nothing that can prevent someone to enjoy the language.. but the 'fn' for me is the most odd.. getting used to it
Even in good C codebases that run static analysis, there are errors due to mixing up "memcpy()" and "memcmp()". UT4 is one example where this happened.
We can't sugar it out completely because there are multiple types of iterators: ".mut_iter()" and ".move_iter()" for example just on arrays.
> Even in good C codebases that run static analysis, there are errors due to mixing up "memcpy()" and "memcmp()". UT4 is one example where this happened.
Well, that particular error would be unlikely to happen in Rust because the return types are different, and there aren't the implicit coercions that there are in C.
If anything the terseness of keywords seems almost necessary because rust expressions seem to tend towards the wordy whenever type annotations come into play, and I'm just glad it didn't devolve into haskell-style symbolics for everything.
https://www.powerbasic.com/support/help/pbcc/let_statement.h...
I remember not having to write let in Basic even in eighties. Anyway, since 1995, http://en.wikipedia.org/wiki/Limbo_(programming_language) people have an idea that there is a simple way to have "define and assign" that is different from "assign" without requiring a keyword: use := for define and assign, x:=exp is equivalent to let x=exp, use = for just assign. Google Go just reused that idea. Even if it's used today by Go, I still don't understand why Rust avoids it, because it makes sources significantly more readable: the variable is not obstructed by the keyword.
let mut is even more "stuff" before the variable in the language where declarations otherwise always follow the variable (which is good!) I'd use x := expression and x : mut = expression.
"x : mut = expression" wouldn't work in Rust because types follow :, and "mut" is not part of a type. "mut" is needed after each binding because one pattern can contain some mutable bindings and some immutable bindings.
Regarding "mut is not a part of a type," so what? From my perspective (of somebody who implemented just much simpler languages than Rust) it's something like a "storage attribute." It doesn't prevent me to build the syntax to be more readable: it's a keyword anyway. It's certainly not that it can't be there because otherwise it's unparsable.
Having x = expr, x := expr and x : mut = expr make all pretty clean and obvious to me...
let (a, b) = get_tuple_of_two_ints();
I don't see that it's fundamentally different to parse if it would be written like this: (a, b) := get_tuple_of_two_ints();
It's true that the parser has to wait for := to do know the semantics of the expression on the left side of it, but it can definitely be done.For example:
struct Foo { x: (uint, uint), y: uint }
let foo = Foo { x: (1, 2), y: 3 };
let Foo { x: tuple @ (a, b), .. } = foo; // => a == 1; b == 2; tuple == (1, 2)
In terms of mut: let (mut x, ref y, &z) = (1, 2, &3);
x = 2; // reassignment is ok, because we bound `x` as mutable
x += *y; // we bound `y` as a reference, so it must be derefed first
x += z; // we used a `&` pattern to get the value stored in the reference, so
// derefing is not necessary
*y += 1; // this would not compile, because y is immutable foo := Foo { x: (1, 2), y: 3 };
Foo { x: tuple @ (a, b), .. } := foo;
( x: mut, y: ref, &z ) := (1, 2, &3);
xx: mut := 33;
I see that having "x: mut" has one character more than "mut x" but I don't believe it makes the text less readable, quite the contrary because it's certainly less surprising when everything that describes the variable follows the ":"Thanks a lot, I've certainly learned more about Rust this way.
let mut x: int = 2;
let x: &mut int = &mut 2;
let (mut x, ref y, &z): (int, int, &int) = (1, 2, &3);
The pattern binding goes to the left of the `:`, and the input type goes to the right. Also note that you can pattern match on types too: let (mut x, ref y, &z): (_, int, &_) = (1, 2, &3);Is the last example equivalent to
let (mut x, ref y, &z): (_, _, &_) = (1, 2, &3);
and can we go even further let (mut x, ref y, &z) = (1, 2, &3);
If we can, when do we actually need specifying the types? To do the type conversion or what?Probably because I don't know Haskell (sorry) but I still imagine x y and z as names and even mut, ref and & as the "access" specifications, which define somehow the "type" of x y and z and not the type the thing accessed with the given name.
let (mut x, ref y, &z): (int, int, &int) = (1, 2, &3);
let (mut x, ref y, &z): (_, _, &_) = (1, 2, &3);
let (mut x, ref y, &z) = (1, 2, &3);
The types are still the same in each case. No conversions or dynamic typing is happening in any of them - they are all statically typed.Rust, like Haskell and ML, uses an advanced form of type reconstruction called Hindley-Milner[0] type inference. This is a constraint solving algorithm that can deduce types from very little information. Sometimes you do need to add some annotations though to help the inference along a little, but that is very rare (although more common in Rust than in Haskell or ML).
The grammar could be expressed as:
'let' <pattern> (':' <type>)? '=' <expr>
Where `<type>` is the type of the expression to the right of the `=`. As I said before: in practice it is very rare to explicitly annotate a `let` binding. *( arraybase + f1( d ) * f2( e, f ) + f3( g ) ) = x;BTW, I really like the basic goals of Rust, namely making something directly linkable with C but having as much as possible safety and more modern features. And I appreciate the work of the developers of Rust. Even introductory materials would benefit by promoting what we can do that wasn't as safe to be done in C.
pcwalton: after getting over the hump of Rust's pointer semantics, I've found that it's a very elegant and fairly simple language. So I was partially wrong about my assessment of Rust as a "complex" language (I say "partially" because the pointer semantics do add a significant layer of unavoidable complexity).
I'd encourage anyone put off by the complexity of Rust's pointers to just push on through. I think you'll find it's worth the trouble.
Rust is definitely the language of the future for games, embedded programming, system programming and other areas of programming that require a high level of determinism. I can't wait until it's stable enough to use for real projects (hopefully sometime this year).
Definitely, I cannot stress this enough. Back when I first looked at the language in 0.3 it was awfully complex and I had doubts about hwo successful it would be. But ever since 0.4, with the removal of classes and argument modes, the core devs have been working extremely hard to make the core semantics of Rust as simple as possible, whilst at the same time making the language powerful and extensible. It will take time to dispel the myths still hanging around though, so I would urge you to share your experiences more widely.
`||` is the logical or[0], why would it lead to less confusion?
(and yes, `|` is the bitwise or[1])
[0] http://static.rust-lang.org/doc/0.10/rust.html#lazy-boolean-...
[1] http://static.rust-lang.org/doc/0.10/rust.html#bitwise-opera...
y if y < 20
In the match block, can't we just use x?