A taste of Rust
lwn.net
lwn.net
Thus, while it is impossible to get a runtime error for a NULL pointer dereference, it is quite possible to get a runtime error for an array bounds error. It is not clear whether this is a deliberate omission, or whether it is something that might be changed later.
I presume that's because the Rust designers didn't want to include a dependent type system. Comments from pcwalton?
1. Dereferencing a raw pointer.
2. Casting a raw pointer to a safe pointer type.
3. Calling an unsafe function.
I was expecting something more along the lines of Safe Haskell (http://www.haskell.org/ghc/docs/7.4.1/html/users_guide/safe-...) which is more about trust than memory safety and, as such, explicitly restricts IO.This isn't a value judgment. I just find it an interesting distinction.
unsafe(no-bounds-checks) {
...
}
or: unsafe(raw-pointers) {
...
}
Right now the problem is that once you drop into an unsafe block, everything that is unsafe becomes allowed. Obviously this is bad for correctness and bad for readability.I guess part of the issue is, what is the goal of marking code as unsafe? The use-case that C# was trying to serve was that you could be consuming a third-party assembly without access to source, and you want to know what are the possible consequences of running it. If Rust's main goal is being able to audit your own code for safety (either by hand or by tools), rather than trusting external binaries, then it makes more sense to take a feature-oriented approach rather than a consequence-oriented one.
Rust's pointer taxonomy might also help with providing degrees of consequences. You could, for example, restrict that raw pointers could only be cast to pointers with non-sendable kind, which would let you circumvent type safety while still controlling the scope of possible data races. Restricting lifetimes might be useful too, but that's too far beyond the limits of my experience.
Basically, it's a way to be able to find the unsafe code by grepping for it. It's a mechanism for security auditors to be able to search for the unsafe code and audit it.
It also serves as some social pressure to avoid unsafe code.
You can find this in Ada, Modula-{2,3}, D, Oberon and many others.
The parent comment: "rust unsafe and haskell unsafePerformIO are different!"
Your comment: "You forgot about unsafePerformIO! It's the same as rust unsafe!"
Add that if a proof system (that is powerful enough) was available, and people actually used it, the language could do away with the unsafe overrides. It could also be of great help to the compiler to optimize the code, like removing array bounds checks where the programmer has supplied a proof that the index is in range.
Some compromises had to be made, e.g. the lack of tailcalls is a regrettable but nonetheless well justified decision. But why didn't they include a standard indentation style in the language specification itself and get rid of the cruft? They have one for Servo anyway, which is the raison d'etre of the whole language, so it's bewildering to me why it isn't included as a part of the specification.
It just pains me to see a promising new language being burdened with an ugly looking syntax when the language itself is quite elegant.
you \
to \
write \
so \
many \
backslashes.
They are not the ultimate solution for block definition and line breaking. You must be very used to them, and that's why you find the lack of it irritating, but that's another story.
Disclaimer: I code python every day of my life nowadays, I'm not a python hater.
blahblah = long().invocation_of.something(with,
lots, of, parameters)
which is generally more idiomatic. (Of course one wishes to avoid long lines at all but that's not always possible.)Or to put it another way, I'd much rather have to syntactically announce a line-continuation (which happens rarely, and is thus an atypical event that is worthy of note) than announce the end of a statement (which happens every single statement, and is thus to me much less worthy of note).
My reasons for preferring indentation-as-block over brace block-delimiters are similar: The indentation is a much more subtle, unobtrusive, un-noisy alternative to braces.
Furthermore, when I code in C or C++, I indent (and brace-delimit) everything properly, according to a style guide; so if I'd be indenting the block anyway, why not make the indentation syntactically meaningful, to save me some keypresses and slightly decrease the syntactic noise?
Plus, for bonus points, brace-less blocks mean I never need to play the brace-matching game. ("Have I got an extra/missing brace in there somewhere? Put the cursor on the brace and tap `%` until I work out what's out of place." True, it doesn't happen frequently if you keep your blocks and functions small, but I'm quite happy to avoid it altogether.)
The general rule is, as with Python and Go, that open braces, brackets, parens or quotes have precedence; and operators or punctuation at the end of a line will continue to the next line.
In addition, there are some complicated parsing internals -- not just for expression-ending sensing, but also for supporting parens-less method calls -- that endow the parser with some compiler knowledge about what tokens are variables and what tokens are methods. In other words, the parser has information that violates the parser/compiler separation of concerns, but it works really well in practice.
The only time I ever use \ in Ruby is for string literals that need to be broken up for readability:
my_string = "This is a very long string that" \
" must be wrapped."
You can use + or <<, but this will be evaluated at runtime, whereas \ results in a single string allocation.Of course, Ruby requires "end" for a lot of constructs, but it could have just as well have used braces.
(forces,
you,
to,
...
)Yes, it's childish. Worse, it's bikeshedding.
MS Research did this right with the experimental hardwhite support while F# was still in alpha. People liked it so much that it became the default style for the compiler and the standard library. It would be impossible to try to make the switch now that the language is widely used.
It might be childish for me to choose one language for a project than another over trivial things like semicolons, but for the language itself it's an irreversible decision once the spec gets set in stone.
I wonder whether language syntax could be considered such a "feature" -- that is, the Rust creators would consciously prefer to combine existing syntaxes (at this point, mainly C++, Haskell, OCaml and Ruby) than invent their own.
An argument could be made that they could just "use Python's syntax", but perhaps a counter-argument would be that Python's syntax would need to be extended so much (for types, pointers, templates, etc.) that the result would effectively be a new invention.
pcwalton says it was to attract C++ devs, which is interesting considering that Go was also designed to attract C++ devs and made the opposite choice. As a sometime C++ dev, I recognize the historical uselessness of semicolons and would love a replacement language that did away with them.
let x = ref 0 in
(* something *)
in Ocaml is not much worse/better than let mut x = 0;
// something
in Rust. As I said above, that's really a minor, stylistic issue, one probably would write "let x = mut 0 in" for a week or so (coming from OCaml) and that's it, after a week it becomes as natural to write semicolon as it is to write 'in'.You could make it available to the Rust community (and even the broader Rust-interested community on HN and Reddit) and see if it takes off.
If the Python-Rust were to become more popular than brace-and-semicolon Rust, the creators may well be persuaded to adopt it. Or at the very least, you'd have a thriving like-minded Python-Rust community. :)
As a sometime C++ dev, I fully recognize the utter inanity and burden of typing semicolons where the compiler should know perfectly my intent, and would rejoice in a language that did away with them. In fact, it's one of the features of Go that I really like. Rust's syntax, on the other hand, feels like a step backwards in some ways.
I wrote a bit more here: https://news.ycombinator.com/item?id=5608157.
More practically, it seems to remove a class of errors. Semicolons are optional in Javascript and I've seen posts here about subtle errors that can creep in when you change code a little carelessly.
And even more practically, it allows you to type "one liners" into things like REPLs even if they aren't one liners.
Just my two cents.
Furthermore, you present a false dichotomy here. A semicolon-free language can easily have semicolons as an optional measure if you do want to cram several statements together on the same line. You mention one-liners; a semicolon-free language can support semicolons. Here's a Ruby one-liner:
>> b = Box.find(3); t = Thing.new; b.add_thing(t)
A class of errors: Well, anecdotally, I have personally never had a single issue with semicolons in JavaScript in all my years developing with it, simply because I always use semicolons. The reason is that JavaScript has a bunch of "semicolon insertion" rules that are not well understood, so dropping the semicolons is a bad idea, as the recent "fiasco" showed, and I just decided very early on not to go that route.As I understand it, JS is a semicolon-enforced language that allows you to drop them at your convenience, whereas Ruby, for example, is a semicolon-free language that allows you to include them at your convenience. I'm not a parser expert, and I won't swear there is a significant difference except in which rules are defined. But I do know that semicolons are categorically not an issue in Ruby. So I would say that this argument is invalid, too, because it presents a flawed language as the ideal. The Rust guys should not need to base their design on JavaScript.
if (something()); { somethingOther(); }
The semicolon seemed to shortcut the whole if-block. I don't know why that thing is even allowed by the compiler.
As for the second {}, its to allow you to create inner scopes where you can declare variables with a more restricted scope without needing to do something silly like "if(true)".
Empty block statements are OK sometimes with while and for loops when the real action that matters is in the condition:
while(!trySomething()){} //empty body for the loop
And since these language for (i = 0; a[i] != x; i++) ;
to find the first index equaling x.Personally, I always use braces in conditional and loop statements, so I'll never have the empty statement semicolon. But that's me.
But if you have
if (something()); { somethingOther(); }
Then you have a new problem: you intended the braced statement to execute iff the if-statement evaluated to true. But since you put the semicolon after if by accident, now the braced part will execute no matter what the if-condition evaluates to.
(my point was about unintentional use of semicolon after if-statement - in which case always using braces doesn't seem to help.)
> Basically, ending an expression in Rust with a semicolon ignores the value of that expression. This is kinda weird. I don't know how I feel about it. But it is something you should know about.
If Ruby can do it (and Ruby does it very well with just a few simple rules, even though its lexer admittedly contains parsing logic to deal with the challenge), then Rust can do it.
The other disappointing aspect of Rust is the "line noise" problem. Rust has three types of pointers, and needs a line-noise character for each of them. That, combined with the liberal use of underscore-based identifier names, makes it very noisy. (Ruby also uses underscore-based names, but the relative absence of noise characters keeps it clean.)
In my opinion any new language project ought to have an overriding aesthetics process. Aesthetics is often belittled (as you say, "childish"), but code readability, over time, becomes more important than almost everything else.
I like Rust's design principles, but Go definitely wins the syntax competition for me.
Rust: ~T
C++: unique_ptr<T>I like explicitness, readability and clear, unambiguous syntax. Two small dots at the end of a line completely changing the entire block's meaning is, to me, a violation of those principles.
`~T` and `@T` were felt to be preferable to `std::unique_ptr<T>` and `std::shared_ptr<T>`. You really do type them all the time.
Go actually gets by with just one type of pointer syntax. Go is what a colleague calls "being stupid simple", which is really its strongest asset at this point. It eschews a lot of complexity by not trying to be clever.
The problem with Go's pointers is that no thought has gone into thinking about goroutines as boundaries of data ownership. It's simple to share mutable data between goroutines, and it's impossible to verify (manually or by any kind of analysis) that a program is not a shared-everything pile of spaghetti. This, plus mutable data, makes it impossible to safely implement things like Erlang-style remote task spawning.
What I would like in my ideal language is a single pointer type: A pointer into the task heap. However, pointers may only be shared between tasks by copying or by transfering ownership, except for immutable data, which can be shared freely with no overhead other than normal GC/refcounting. With some COW magic you could even make the copying really fast, too. Then all you need is a simple syntax for limited references that cannot be stored, only copied, so that you can actually have functions that work on data efficiently without leaking their pointers anywhere.
Maybe I am being naive -- I won't pretend to be a Rust expert. But this model works pretty well for Erlang. I find that while Rust is clever, it falls into the other extreme and becomes too clever; and being too clever is a kind of stupidity. For example, borrowed pointers sound neat on paper, but then you find you have to dick around with declaring pointer lifetimes. That's pretty insane. It results in a kind of syntax that represents everything I hate C++ for. I can understand that it took a while to decide on a syntax for declaring lifetimes. It looks totally weird and arbitrary. (Quotes are for strings, man!)
It's also possible that Rust just is not for me. :-)
We experimented with COW designs in the early days. It seemed pretty hard to implement: hardware MMUs operate on page granularity, not on object granularity, so you need (thread-safe!) guards for all objects. That's pretty tough from a performance point of view. Perhaps it can be done, but it seemed less risky to just use uniqueness.
For what it's worth, I know I speak for at least a couple of other people when I say that we were, for a long time, hoping for a "better Go" -- we are mainly rubyists who want native compilation, a better/safer type system, better concurrent performance, more easily massively parallelizable -- and that Rust's development has been going in a slightly different direction than we were hoping. Perhaps it's time to re-evaluate C++11 or even Haskell.
Speaking personally for a moment here, I'm a Python/Javascript/PHP dev by day, and the reason that I'm interested in Rust is precisely because of all the new concepts that it's forced me to learn. I could never get into C because I enjoy having two feet, and I could never get into C++ because I know that there's no escape from that stygian pit. For me, Rust is precisely the right balance of low-level/safety/coherence, semicolons be damned.
Also, Erlang is slow. Really really slow. It's faster than Ruby, I think, but not much. And It sucks at a lot of things that should be fast in a functional language, such as file I/O.
Erlang itself is also neat, but the syntax is abhorrent (commas and periods instead of semicolons!), performance is awful, and the language has many warts (no Unicode, no structs, etc.).
I see where you're coming from. As for me, I am intimately familiar with everything that Rust provides, and it's frustrating because it's close to what I have been waiting for, but it makes a bunch of design choices I disagree with. Same with Go.
For now, Go is much closer to what I want than Rust, but performance is pretty awful for many things, so I'm not a happy clam.
If the goal is simulate n-body problems and solve one instance of sudoku it is not the right tool. If one needs a fault tolerant system to maintain 2M concurrent connections to clients then it is the best tool for the job.
http://www.erlang-factory.com/upload/presentations/558/efsf2...
Think about Erlang as a tank. A cool hipster comes to you says "LOL, my fixie bicycle is sooo much faster than your tank" and he's right because in some cases you just want to go to the nearest coffee shop to pick up an espresso. However a time comes when you need to go into battle and putting a turret and shields on the bike only works so far, then it is time for a serious tool for the job.
> performance is awful
I don't know I wouldn't call something that can handle millions of concurrent connections slow. It is slow if you all you need to do is handle a single connection at a time.
Fault tolerance is actually the main feature of Erlang everything else flows from it. You don't always need it but when you do need it there is nothing like it. Fault tolerance mean paying some performance penalty.
> syntax is abhorrent (commas and periods instead of semicolons!),
Don't those make more sense though? How does a semicolon ending a statement make more sense than a period. And if there are 3 things that are executed then comma seems quite natural as well. On the other hand few languages rival Erlang's pattern matching.
> no structs
Frames/Maps are coming next year as part of R17
For example, not every app has a need for Erlang's massive parallelism. I can't write desktop or iOS apps in Erlang. Small utility scripts are cumbersome. There seems to be no kind of strong graphics or game support for it. For web apps the lackluster performance means is not really a step up from Ruby, which I currently use, so in that area there is no reason for me to switch.
In fact, the only area where Erlang is really perfectly suitable is in developing fault-tolerant, concurrent, parallel, distributed systems. And yet when I do need to write such a system, I look to Go instead.
> Don't those [commas and periods] make more sense though?
No, they really are a bad choice. First, any kind of statement terminator/separator should be unnecessary, as I have argued elsewhere. Secondly, distinguishing between two types of terminators is a bad idea because it makes editing harder. If I have
A,
B.
and I want to switch the order to: B,
A.
then I can't just use my editor's nifty line-transposing mechanism. I have to edit each line and change the "," into a "." and vice versa. It's incredibly badly thought out.Really, I like Erlang, but whenever its syntax is discussed, its defenders come out of the woodwork to make arguments without ever really wanting to listen. Syntax does matter. From speaking to lots of people, I know that Erlang's antique and cantankerous syntax is the number one problem preventing people from adopting it. As long as erlangers do nothing about their syntax, it will remain a weird little language that people admire but don't use.
Just look at Elixir -- people actually get excited about it. Because it has a nice syntax.
> few languages rival Erlang's pattern matching
Erlang's pattern matching is cool, but its reliance on ordered lists (which it calls tuples even though the fields are unnamed) makes it less cool again. I vastly prefer Haskell's pattern matching and overall approach to typing.
Complaining about braces and semicolon is like complaining about parenthesis in LISP. Learn to appreciate the strength of a language and move beyond its syntax.
Last I checked Rust took about 40 minutes to compile itself on my laptop. Is it still the case? (I haven't touched Rust for a month or so.)
Also, is anyone using Rust in non-toy projects? (Except Mozilla's own servo.)
Since it's not production ready yet: no
Q^3, which can render Quake 3 maps and will eventually be its own game: https://github.com/Jeaye/q3
Servo, an experimental web browser being written by Mozilla: https://github.com/mozilla/servo
That said, rust has been written in rust, and every commit is tested against our test suite on Linux, OS X, BSD, and Windows. So while the language itself may be changing, any code you write in it at a given point in time should work. It'll just take some (small) effort to keep that code in sync with the compiler.
So if you are able to handle that risk, we'd love to have you try it out. It'd really help us find the warts in the language/stdlib before we lock in 1.0.
If you want to rewrite your codebase on a quarterly to cope with the changes, that's up to you, but I wouldn't.
I'm waiting for 1.0 to really start writing code in it.
I haven't seen this in the documentation.
Modes were supposed to give hints to the compiler whether or not to implicitly copy or move a value into a function, but it was buggy and confusing. So we replaced it with always moving for unique types, and always copying for reference counted types.
In addition to the +foo forms, there were also others: -foo, &&foo, ++foo, perhaps more. Be glad that they're gone. :)
(Also, the master branch is not the one to be looking at for the latest changes at the moment: patches are merged into 'incoming', and master hasn't been updated for more than a month.)