Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
A Week with Mozilla's Rust (relistan.com)
180 points by lengads on Aug 21, 2013 | hide | past | favorite | 102 comments


The example of match usage is really poor. From the looks of it there is no "pattern matching" going on at all here. I don't know the syntax, but even something like this would be a better demonstration:

    let computed_key = match (key.len() > self.block_size, key.len() < self.block_size) {
        (true, false) => self.zero_pad(self.hash(key).digest),
        (false, true) => self.zero_pad(key),
        (false, false) => key
    }
At least there is some matching here, unlike in the original example. But in real world this should be if..else if..else statement, not match - the latter being used makes no sense if there is no matching going on. In Erlang there is an if statement, which is a 'case' (equivalent of match here) but with matches taken out and only guards allowed - that's what would fit here, but I doubt Rust has something like this.

Also, does the compiler complain about my example above being not exhaustive? I think in OCaml it would, which is sometimes nice.


>but I doubt Rust has something like this.

Oh, but it does! There's the cond! macro:

    let computed_key = cond!(
        (key.len() > self.block_size) { self.zero_pad(self.hash(key).digest) }
        (key.len() < self.block_size) { self.zero_pad(key) }
        _                             { key }
    );


As noted by masklinn, the compiler will complain about the non-exhaustive match. Stuff like this makes me want a dependently typed language for practical programming, in which I can express the concept of "this case ought to be impossible", and convince the compiler by supplying a proof. Also, this would eliminate the false dichotomy between "fast, unchecked, unsafe array indexing" and "slow, checked, safe array indexing", which would be a huge win for safe systems programming.


There are some languages heading towards this. ATS [1] provides a restricted form of dependent types [2] that allows you to do what you want and is designed for systems programming. You can do safe pointer arithmetic for example.

Idris [3] is another language with dependent types (and is more Rust-like) designed for systems programming. I don't think Idris has type level presberger arithmetic though making some things a bit verbose.

[1] http://www.ats-lang.org/ [2] http://bluishcoder.co.nz/2010/09/01/dependent-types-in-ats.h... [3] http://www.idris-lang.org/


I assume you're familiar with ATS? What don't you like about it?


I am not the original poster but I'll respond to this one:

I am familiar with ATS and I don't like that I am only familiar with it from the Language Shootout contest website. I haven't heard of it used in production, no blogs, no news on HN.

It is a bad excuse and rather sad but my main charge against it is that it is not popular enough.


I agree not much is heard about it. I blog about it a fair amount on [1]. There's also /r/ats [2]. I use it in production. The backend of my bitcoin mining pool [3] is written in ATS and has about 1% of the total bitcoin mining capacity and has been operating for a couple of years. The front end of the pool is written in Ur/Web.

[1] http://bluishcoder.co.nz/tags/ats/ [2] http://www.reddit.com/r/ATS/ [3] http://mmpool.bitparking.com


No tactics.


Dependent typing would be throwing out the entire type system, and also be incredibly ambitious. Dependent type systems are great, but I think the Rust team made the right decision here.


> Also, does the compiler complain about my example above being not exhaustive?

Yes, although the error message isn't very good:

    crypto.rs:55:23: 59:5 error: non-exhaustive patterns: true not covered
    crypto.rs:55     let computed_key = match (key.len() > self.block_size, key.len() < self.block_size) {
    crypto.rs:56         (true, false) => self.zero_pad(self.hash(key).digest),
    crypto.rs:57         (false, true) => self.zero_pad(key),
    crypto.rs:58         (false, false) => key
    crypto.rs:59     };
    error: aborting due to previous error
And technically that's not a good thing, since (true, true) is not possible considering the input.


it should be expressed with conditionals anyway.

    match key.len() {
    k if k > self.block_size => ..
    k if k < ... => ..
    k  => ...
or even

    match key.cmp(self.block_size) {
    Equal => ..
    Greater => ..
    Less => ...


Absolutely.


Thanks for the tip!


You're right that it's not an ideal example. I think I said that in the post. The context of the article was the week I spent with Rust and the project I actually did with it. There was not a better example in my code to show. I could have contrived something, but it wasn't really in spirit of what I was writing about.


You probably should fix this in your code too. As noted in this thread, that's not a proper way to use pattern matching. Anyone reading your code will be confused by this usage and that's not a good thing. It probably would be best to use this:

    match key.cmp(self.block_size) {
        Equal => ..
        Greater => ..
        Less => ...
    }
Which is much prettier (and more succinct) than both your original code and my example. You could also use a `cond!()` macro here, but that would mean you don't have an example for a blog post, so I think the above code is the best option. Thanks to kzrdude for posting.


I don't want to turn this into a Go vs. Rust discussion, because the two languages are very different and have very different goals.

However, I want to question the claim:

> Go is also not particularly friendly to interface to external libraries written in C

which doesn't seem to be explained anywhere in the article, and jars with my understanding.

Go does play very well with C; cgo[0] makes this pretty straightforward.

Maybe there are specific things that the author is trying to do that they found difficult in Cgo, but I would say that Go does play fairly nicely with C; that was a design goal.

Here's a simple example of cgo in action: http://golang.org/doc/articles/c_go_cgo.html. As you can see, it's relatively straightforward and easy to use.

[0] http://golang.org/cmd/cgo/


Rust not having the garbage collection as far as I understand can be made to produce routines that are part of C application. As far as I understand, by design Go can't be a "nice" citizen in a C application, the Go routine will bring with itself the whole city it lives in to your flat.

So Go applications are something like "compiled, fast Ruby or Python." As far as I understand, Rust should theoretically be a way to write a piece of your application (even mainly C application) in something that gives you a different kind of expressibility. If there's need for that is another question, specifically, I'd like to see some examples that would really impress me. The examples the article author provided give me only the impression "OK it's weirder syntax but it's not so far from what would have to be written in C anyway, so where's the advantage?"

What I consider missing for "properly" interfacing with C is for Rust to be able to slurp C headers as they are, without the need for additional "translation" files that are presented in the article. At least the headers without the definitions of the functions. But the declarations of the structures and functions and even basic preprocessing were really, really convenient.


> The examples the article author provided give me only the impression "OK it's weirder syntax but it's not so far from what would have to be written in C anyway, so where's the advantage?"

Memory safety.


And type safety, even when writing macros!


I've took a look on one "real" example, I admit I haven't spent much time with the language, I just want to get the idea looking at the code which really does something, I like that more than starting with boring tutorials:

https://github.com/mozilla/rust/blob/master/src/libextra/jso...

I know it's the least interesting thing, but for me the simplest rule to recognize Rust vs Go is that at the moment Go has the nice and minimalistic := and Rust the clumsy (for my aesthetics at least) "let."

Still, I understand that Rust attempts to solve more than Go in some aspects, and I really hope it can succeed. For that, yay Rust!

Can somebody explain me the reason for constructs like this:

    self.error(~"trailing characters")
vs

     self.parse_ident("ull", Null),
I read that ~ means "owned box on heap." Why should something that are facto string literals be owned and on heap? Isn't the need to allocate and box string literals on the heap something that makes the language unnecessarily slow? And why there's a need somewhere to explicitly box something that's known at the compile time and somewhere there isn't?


> I know it's the least interesting thing, but for me the simplest rule to recognize Rust vs Go is that at the moment Go has the nice and minimalistic := and Rust the clumsy (for my aesthetics at least) "let."

It is not possible for us to have ":=" because we don't know whether to parse a pattern or an expression without a prefix token like "let". Rust's pattern language is far more expressive than Go's and one grammar will not cover both.

> Why should something that are facto string literals be owned and on heap? Isn't the need to allocate and box string literals on the heap something that makes the language unnecessarily slow?

Probably the `error` method explicitly asked for a heap-allocated string, perhaps because it wants to pass it to someone who will later free it. (Unlike in C, it would be a type error to attempt to free a constant string in read-only memory, which is what a plain string literal is in Rust.)


Does it mean that to do

    self.error(~"trailing characters")
there has to be the complete allocation of enough memory to store the copy of the whole string, then copying of all characters there, only in order for the target function to be able to do "free"? If the intention of the language is to "properly" work with a lot of strings (and it should be) it would be good not to have glaring inefficiencies? Wouldn't it be a good optimization to have the free routine check if the pointer is inside of the static area and then not do anything. That way you can pass the pointer to the static area avoiding copying and also avoid freeing. And the only thing needed is that free has "static_begin" and "static_end" addresses?

There are also other possibilities -- by knowing that the heap allocated things have lower bits of addresses 0 you can mark the stuff using the lowest bits of pointers.

I know, I have maybe too low level approach. But why not, as soon as you want to be more convenient than C, you have to think low level too.

Still what I like is that at least at the moment this explicit declaration of boxing gives a nice feeling that nothing is done "behind the back" of the programmer, which is good -- the main problem of C++ is that you can't know if somebody hid something very nasty behind some plain innocent looking construct, or even what the compiler will implicitly do or won't.

EDIT-addition: Regarding "pattern" if the := would be a single token, not allowed in patterns, wouldn't it be OK then? Do you have such a valid sequence in the patterns as the combination of the single operators or whatever?


> If the intention of the language is to "properly" work with a lot of strings (and it should be) it would be good not to have glaring inefficiencies? Wouldn't it be a good optimization to have the free routine check if the pointer is inside of the static area and then not do anything.

You can implement that yourself, by using an enum for example or by using a custom smart pointer. The moment you start adding more magic to "free" beyond "call free" you become less low level of a language.


Can I ask in the language if the address points to the item constructed by the compiler in the static area?

The thing I miss most in C is the possibility for the some kind of"introspection" -- reaching out to the info that the compiler or linker has to know anyway. As far as I know D language is very good for such things.


You can use lifetimes to enforce that a certain thing is static data, and combined with an enum, you get the best of both worlds: compile-time constants require no allocations, but still flexible enough to allow run-time construction:

  enum StringRef {
      Static(&'static str),
      Owned(~str)
  }
and then `error` would take `StringRef` and be called like:

  self.error(Static("trailing characters"))
  // or
  self.error(Owned(fmt!("%u trailing characters", count)))
(There was even a pull request that added this and the corresponding one for vectors to the stdlib, but it didn't landed (yet): https://github.com/mozilla/rust/pull/7599)


> Ca I ask in the language if the address points to the item constructed by the compiler in the static area?

It'd require some OS-specific magic. But you could probably do it.


I'm new to Rust and I'm not familiar with this code, but here's an attempt at an explanation anyways:

The "error" function returns a struct containing the error string. I suspect the intention is to allow dynamically constructed error messages like "syntax error at line 4, char 3" (although it doesn't seem to be doing this anywhere). Since the string might be dynamically allocated, it has to be freed when the struct is freed, which obviously can't happen if the struct just has a pointer to the literal in static memory. (You could flag the string somehow as "should be freed/should not be freed", as you suggest in your other comment, but this has its own tradeoffs)

If you wanted an "error" function that would take string literals directly (and only string literals) you would do something like this: https://gist.github.com/bct/6300740

The "parse_ident" function doesn't return any portion of the input string, so it can just use a borrowed reference.


If you wanted an "error" function that would take string literals directly (and only string literals)

And how about some convenient "either or"?



~str is mutable. error probably doesn't need a mutable string, though. either it's just an overlooked API (json is an old part of the codebase, doesn't really see a lot of attention) or what pcwalton said about calling something down the line.


~str isn't inherently mutable; it's an owned string, and so is mutable if placed in a mutable variable.

The reason it's used here is so that the error messages can be constructed at runtime e.g. `fmt!("line %u: trailing characters", line_number)`, if it was using &'static str (i.e. a compile-time literal) then one could only use hard-coded error messages.


  > What I consider missing for "properly" interfacing with 
  > C is for Rust to be able to slurp C headers as they are, 
  > without the need for additional "translation" files that 
  > are presented in the article.
rust-bindgen (modeled after Clay's bindgen tool) has you covered:

https://github.com/crabtw/rust-bindgen/

C header files go in, the proper Rust definitions come out. There are long-term plans to adopt this as an official tool to be distributed with the compiler.


I'd prefer such code to actually be the part of compiler, simply, C headers directly readable by the compiler as soon as they are referenced from the code as C headers.


That should be possible to perform automatically for simple libraries once bindgen is officially in the compiler (again, there are long-term plans for this sort of automatic binding step). But bindgen can't do everything: if you have C functions defined as macros (which so many C libs seems to do, extensively) then you're SOL and will end up needing to implement those functions manually anyway.


This is roughly what cgo does, but cgo AFAIK (at least, in 1.0.3 in earlier) does not fully comprehend C preprocessor macros, which are used all over the place in some libraries to define both functions and structs, largely for compatibility reasons. Taking a random example, OpenSSL's EVP_CIPHER_CTX_block_size is a macro that simply returns an internal struct field; to call it from Go, it needs to be explicitly wrapped into a C function. I presume this is because cgo can't infer the signature.

For other things, like some of the constants ncurses defines, it's much less clear what's going on (specifically, I had a hard time with any macro defined with the NCURSES_ACS macro, though that was pre-Go1 -- I believe cgo can now use these macros directly).

There's an additional impedance mismatch when the C headers use macros for conditional compilation (for, e.g., cipher suites that can be optionally compiled out), but it's not a worry if you either are conservative with the functionality you use, or control the packages installed on a system.

I haven't had a chance to play with Rust yet, but since it uses C linkage directly for it's FFI layer, I suspect these problems are less easily addressable.


The only way to do this fully automatic is to integrate a partial C compiler, able to understand the pre-processor and all types of declarations.


There was/is a plan to use clang to help with this.

(In fact, rustc used to build clang as well as LLVM, but this was disabled since it wasn't being used (yet).)


This is the approach also taken by DStep for D.

https://github.com/jacob-carlborg/dstep


As far as I understand, by design Go can't be a "nice" citizen in a C application, the Go routine will bring with itself the whole city it lives in to your flat.

That is correct, but the GP is correct as well. Using C code in Go is easy(just use cgo etc.), vice versa not so much(no such thing as goc etc.).


At the bottom of the cgo command documentation [0] is an example of exporting go functions to C. I don't fully understand how those functions work when running in C land (does the GC come with?), but the implementation doesn't look difficult.

[0] http://golang.org/cmd/cgo/


What you can do:

  - Have some C code in your Go app that calls a Go function.
What you can't do:

  - Have some C code in your C app that calls a Go function.


The second answer to this stack overflow [0] indicates that you can run Go functions from C if you use gccgo [1].

[0] http://stackoverflow.com/questions/6125683/call-go-functions...

[1] http://golang.org/doc/install/gccgo


How it works (from http://golang.org/src/pkg/runtime/cgocall.c )

    36	// The above description skipped over the possibility of the gcc-compiled
    37	// function f calling back into Go.  If that happens, we continue down
    38	// the rabbit hole during the execution of f.
    39	//
    40	// To make it possible for gcc-compiled C code to call a Go function p.GoF,
    41	// cgo writes a gcc-compiled function named GoF (not p.GoF, since gcc doesn't
    42	// know about packages).  The gcc-compiled C function f calls GoF.
    43	//
    44	// GoF calls crosscall2(_cgoexp_GoF, frame, framesize).  Crosscall2
    45	// (in cgo/gcc_$GOARCH.S, a gcc-compiled assembly file) is a two-argument
    46	// adapter from the gcc function call ABI to the 6c function call ABI.
    47	// It is called from gcc to call 6c functions.  In this case it calls
    48	// _cgoexp_GoF(frame, framesize), still running on m->g0's stack
    49	// and outside the $GOMAXPROCS limit.  Thus, this code cannot yet
    50	// call arbitrary Go code directly and must be careful not to allocate
    51	// memory or use up m->g0's stack.
    52	//
    53	// _cgoexp_GoF calls runtime.cgocallback(p.GoF, frame, framesize).
    54	// (The reason for having _cgoexp_GoF instead of writing a crosscall3
    55	// to make this call directly is that _cgoexp_GoF, because it is compiled
    56	// with 6c instead of gcc, can refer to dotted names like
    57	// runtime.cgocallback and p.GoF.)
    58	//
    59	// runtime.cgocallback (in asm_$GOARCH.s) switches from m->g0's
    60	// stack to the original g (m->curg)'s stack, on which it calls
    61	// runtime.cgocallbackg(p.GoF, frame, framesize).
    62	// As part of the stack switch, runtime.cgocallback saves the current
    63	// SP as m->g0->sched.sp, so that any use of m->g0's stack during the
    64	// execution of the callback will be done below the existing stack frames.
    65	// Before overwriting m->g0->sched.sp, it pushes the old value on the
    66	// m->g0 stack, so that it can be restored later.
    67	//
    68	// runtime.cgocallbackg (below) is now running on a real goroutine
    69	// stack (not an m->g0 stack).  First it calls runtime.exitsyscall, which will
    70	// block until the $GOMAXPROCS limit allows running this goroutine.
    71	// Once exitsyscall has returned, it is safe to do things like call the memory
    72	// allocator or invoke the Go callback function p.GoF.  runtime.cgocallbackg
    73	// first defers a function to unwind m->g0.sched.sp, so that if p.GoF
    74	// panics, m->g0.sched.sp will be restored to its old value: the m->g0 stack
    75	// and the m->curg stack will be unwound in lock step.
    76	// Then it calls p.GoF.  Finally it pops but does not execute the deferred
    77	// function, calls runtime.entersyscall, and returns to runtime.cgocallback.
    78	//
    79	// After it regains control, runtime.cgocallback switches back to
    80	// m->g0's stack (the pointer is still in m->g0.sched.sp), restores the old
    81	// m->g0.sched.sp value from the stack, and returns to _cgoexp_GoF.
    82	//
    83	// _cgoexp_GoF immediately returns to crosscall2, which restores the
    84	// callee-save registers for gcc and returns to GoF, which returns to f.


I'm not particularly familiar with either language... that documentation seems to be talking only about Go called from C-called-from-Go, not from arbitrary C. In other words, it handles Go calling a C function that expects a callback, and handing it a Go function. This does not handle a fundamentally-C program calling a Go function.

Again, not an expert, I could be mistaken, but this seems to not be addressing the original issue raised (that C/Go interop is one-way only).


> Rust not having the garbage collection as far as I understand can be made to produce routines that are part of C application.

Rust can operate without garbage collection if you don't use shared pointers.

> "OK it's weirder syntax but it's not so far from what would have to be written in C anyway, so where's the advantage?"

Apart from memory safety, a lot of exciting, modern features: pattern matching, typeclasses, immutability per default, built-in concurrency and parallelism, a module system, a packaging system...

> What I consider missing for "properly" interfacing with C is for Rust to be able to slurp C headers as they are, without the need for additional "translation" files that are presented in the article.

I don't know any language with this kind of FFI...


> I don't know any language with this kind of FFI...

Does Objective C count?


I believe he is referring to the fact that you cannot write a lib in Go that you link into a C application.

Note that this is not just a matter of Go's GC and runtime requirement, but simply the fact that they do not support cdecl / stdcall conventions.

There are plenty of garbage collected languages that can be linked into a C program (Lua, Python, even Haskell) - you just have to make extra calls to start-up the runtime.


This question was answered by Armin Ronacher three days ago.[0]

"Why for instance is it not a good idea to write a library in Go? The reason for this is that Go for needs quite a heavy runtime that does garbage collection and provides a scheduler for it's coroutines."

[0] http://lucumr.pocoo.org/2013/8/18/beautiful-native-libraries...


I think it's more of a comparative statement. Interfacing with C in Rust is very simple, as he details in the article.


The keyword here is "friendly", which cgo really isn't.


I never understood why they didn't went the Turbo Pascal/Delphi, Ada, Modula-3, .NET, D way and allowed for proper FFI declarations, instead of forcing a dependency into a C compiler that speaks Go calling conventions.


What about callbacks to/from C code and function pointers? Embedding Go from C? Building shared libs in Go that can be loaded in C?


I enjoyed the article as I haven't been exposed to much Rust yet, but I was disappointed that the intro didn't match the content

The second sentence:

> I want a language where I can be much more productive than in C: one in which I do not fear the correctness of my memory management, and don’t have to go through major gyrations to write concurrent code.

And we see no explicit demonstrations of the memory management and no demonstrations at all of the concurrency model. It feels like the author had a more fleshed out post in mind, but either decided it was running too long or got bored before reaching the conclusion. :\


It feels like the author had a more fleshed out post in mind, but either decided it was running too long or got bored...

Or it could be that this post is just the beginning of his journey of learning & blogging about Rust. He did post another one 4 days later, albeit not yet touching on the topics you mention:

http://relistan.com/a-pattern-for-wrapping-c-function-calls-...


I agree, the actual content doesn't match the introduction so well.

I've been looking into Rust during the past few days and I must say I got stuck on the memory model - all the different pointers and their interaction with ownership and mutability, closures capturing their environment (variables outside the closure in the scope where the closure is defined), reference binding, and then things like std::Cell which on the one hand seems to be an alternative to std::Option but on the other hand it's using some unsafe operations in its implementation to change mutability or something... It's quite confusing and it stopped me from writing a simple example application I wanted to write because I wanted to understand what's going on a bit better first.


> all the different pointers

@ is going to sink into the library and will be deemphasized in the language to help with this. That leaves only ~ (pointers) and & (references).

> closures capturing their environment (variables outside the closure in the scope where the closure is defined)

Right, this is one of the difficult pieces. This is going away as well in favor of Java-like Runnables to make the variable capture easier to understand (with macro sugar to make them just as convenient as closures). The closures that remain will work just like the closures you know from other languages regarding variable capture, and the Rust-specific stuff regarding variable capture will be gone.

> std::Cell

Yeah, this needs to be better documented. Part of the problem is that we haven't totally nailed down what Cell will be used for yet.


A fair critique. I should write more on the subject.


For the same quoted sentence (@relistan):

1. avoid dynamic allocations (limit yourself to stack-only objects) and you'll get rid of those fears for memory management

2. limit yourself to functional programming and you'll get a pretty good concurrent-ready code

For this "I need a new language" stance you'll need some better reasons.


Or... I could use a language/toolset that makes all that easier. Why does that need further justification?


You don't need reasons to chose a different language/toolset. You need better reasons for dismissing something like C/C++ for not being appropriate.


I read the intro as "I like Rust, I'm going to twist my requirements to match", but that may be a bad read of the author.


That wasn't my intention. I didn't decide to like Rust before trying it, I tried it to see if I liked it. I wrote up what I've learned so far. Probably some day this post will seem naive to me, but for now it's the best I could manage.


Fair - if I were to write up what I'd want from a new systems language it would be: statically typed with some sort of generics capability, natively compiled, not GC'd (I like the idea of ObjC's ARC, though I can't say I've used it), and with first-class support for concurrent or parallel programming - this also sounds an awful lot like Rust, and I don't think I was particularly aiming for it, it's just got a good set of features and design goals for a modern systems language.


Of all the upcoming languages out there. I think rust is best positioned. The fact that it is really the only contender that can preform the same role as C, but it folds in lots of the language design theory of the past three decades puts it in a league of its own.


Exactly! This is what makes Rust exciting - as opposed to yawnfests like Go that merely provide a compiled, somewhat faster Python.


I like Rust and Go both, but I think that Go was intended not to be "exciting", but to provide a fairly narrow set of benefits and to be production ready quickly (I'm not really sure if the long term roadmap is for Go to stay there, or use production experience to grow into something with broader advantages, just carefully selected based on needs revealed through production use.)

Rust is a longer-term, more ambitious effort.


Go does have one interesting feature: CSP concurrency built in. Even this was predated by Erlang, though.


I was really a little bit disappointed to learn that go (touted for it's goroutines) doesn't have actors, i would love to have the erlang style, network aware actor model in a language i belief i could learn easily ;) Without that, i don't see so much noveltly :( All in all i don't see how goroutines can compare to the Erlang model at all (


agreed. no fan of Go.


I am no fan of Scheme either, but at least I admit it had one big contribution: the idea that a language ought not to be measured by the number of its features, but by the number of things its features can express when combined.

The situation with Go is completely different. Go is just... meh. Its concurrency model is worse than Erlang's, which predates it. Its type system is worse than Standard ML's, which predates it. Its runtime efficiency is noticeably worse than C++'s, especially so when using customized allocators and avoiding exceptions and RTTI. In short, Go is mediocre.


You haven't mentioned defer/panic/recover, which seems like the strangest aspect of the language to me. It's similar to exceptions, but the mechanism of action is much more complicated semantically.


As I understand it (and I'm not a Go expert), but panic/recover is not idiomatic Go, and they much prefer the style of using a second return parameter for errors. defer is just a useful syntactic construct that becomes complicated when you add in exceptions (but no more so than calling the destructors in C++ stack unwinding).


> defer is just a useful syntactic construct that becomes complicated when you add in exceptions (but no more so than calling the destructors in C++ stack unwinding).

defer is quite a bit more complex than C++ stack unwinding, because it's dependent on implicit mutation of per-function state and can't really be implemented any other way in the general case. This shows up when you call defer repeatedly in a loop, for example.


> The fact that it is really the only contender that can preform the same role as C

Modula-2, Ada, Turbo/Apple Pascal were there first, but sadly lost developer hearts to C.


That seems like a really bad example of pattern matching, as no pattern matching is going on; it's just using the guard statements, and so could be replaced with an if statement:

    let computed_key = if key.len() > self.block_size {
        self.zero_pad(self.hash(key).digest)
    } else if key.len() < self.block_size {
        self.zero_pad(key)
    } else {
        key
    }


So the last statement in a block for a matched if condition gets assigned to the variable? That's pretty cool.


Any language that emphasizes expressions over statements will have this feature. Off the top of my head, that should include Haskell, Scala, CoffeeScript, Ruby, Elixir, Clojure (hell, anything Lisp really)...

The idea is that while you may be able to traverse code-paths with side effects in some of these languages, they encourage treating a line of code (term used loosely, as an expression can span multiple lines) as a computable value. It may take a bit of getting used to at first, but from my own experience, it now feels weird and awkward when a language doesn't.

Reminds me of looking at a nasty language like MUMPS or something, where persistence is built into the core language; statement-oriented languages tend not to be composable.


By leaving off the semicolon, you can make a block (which is anything in braces) evaluate to the value of its final expression. It's one of those rules that seems perfectly terrible at first blush, but actually seems to work really well in practice thanks to Rust's strong static typing.


I don't think it's fair to compare Rust to Go, while operating on nearly the same level. They are targeting a very different crowd. Go has very clean syntax, few reserved words and writes almost as easy as python. This makes go a attractive language for programmers who have a history with languages like php & python but also want more speed. Rust on the other hand has a more complex syntax but allows you more control over memory, thus making it more attractive for those with a history in C or C++. Go is a good language to learn as your first compiled language, easy to write, easy to learn and a good ratio between performance and effort. Rust is (or will be when it's stable) a good language to learn when you have a history in C and you want memory & type safety but still the control you are used to in C/C++.


I enjoyed the article very much. Please continue with your journey of explaining Rust to programmers that are looking for new alternatives to C.


I really appreciate that feedback. Thanks!


I must say i am really surprised about the presented features of the language. It seems to take a lot of the nice things of more dynamic languages and puts them into a fast, compiled system language. I will have to try Rust, now!


Rust looks really great and I would love to switch all of my C development over to Rust but last time I tried to compile it, it took a good hour or two. Has this changed at all?


Well, we have a production-quality optimizer (LLVM). So it'll always take some time to compile.

Eventually once all of our LLVM patches are upstream and the versions including the patches make it into common repositories you may be able to use your system LLVM to compile the Rust compiler, at which point the compile times will drop dramatically.

We continue to work on compile speed of Rust code all the time.


Will you also work on providing a better Windows package? I don't understand why you don't just bundle all the dependencies, given that they all seem to be open source and redistributable.

Call me spoiled but I expect a one-click installer in this day and age. I still haven't tried to actually use Rust because of that and I am sure I am not the only one.


You're spoiled :P

But, windows is (unfortunately) a bit of a second class platform at the moment (although there have been some very big strides in the last week or so). So part of the reason for poor windows packaging is Rust isn't ready on windows yet (even more so that its normal pre-alpha-ness on Linux and Mac).


Why bother working on a proper distribution of a package that isn't anywhere near ready?

It will come in its due time.


I hope Rust can eventually reach compile speeds of most module based languages.

Every time I update my netbook with a new Rust release it takes a few hours, it almost feels like I am compiling KDE or something.


The compiler is pretty slow (although there's a big drive starting a few days ago to get certain aspects down (mostly compiling small files); because running the testsuite is so slow that work on the compiler is getting dragged down), but compiling the compiler itself is particularly bad:

- it has to build LLVM: Rust currently has to use a custom version; the goal is for this to be unnecessary so that the system LLVM is ok.

- it has to build itself and the standard libraries 3 times: rustc is written in Rust; so it has to bootstrap.

- it eats a lot of memory, so bootstrapping on low memory systems is bad. Much of this is historical; the bootstrapped code means that large portions of the compiler are written when the language was less developed, and so is very non-idiomatic. It's progessively being modernised

The first and last can be fixed; the second can't, at least until there are binary releases.


I understand the reasons behind it.

Given my experience with compiler development, back when I was in the university, my expectations is that the second point would be pretty fast anyway, if the first and last issues are solved.

I have good experience with languages that use modules, some of them had bootstraped compilers like the Oberon family.

That is why I said I am hoping for these issues to be solved, as I think building LLVM is the main culprit.


What does it use for the first-stage bootstrap, out of interest?


There are periodic (made when someone feels like it, normally every one or two weeks) snapshots, i.e. a normal bootstrap saved as a binary, of the current master branch, which are uploaded to rust-lang.org, and downloaded (once, they are cached) when building a revision that requires that snap.


Why do people keep reinventing syntax? Quick tell me what the following mean:

struct Digest { digest: ~[u8] } // what's ~ here?

for self.digest.iter().advance |&byte| { acc = acc.append(fmt!("%02x", byte as uint)); } acc

What does the above mean? acc as a separate line and nothing else

I hate it when people reinvent the same construct that can be found in other languages but differently. I guess they want to do something different in their language, is it?


Rust does have some new syntax, but I think most of it is justified for making programming in it nicer.

The `~[u8]` would be written `std::unique_ptr<u8>` in C++. They're used everywhere in Rust, and having to write out unique_ptr would get awfully tiresome.

The `acc` on its own line is a result of "the last statement gets returned" like many functional languages do. If you were writing C++, you'd write `return acc;` instead. This one is more of a convenience, and you could get away without it.


> The `~[u8]` would be written `std::unique_ptr<u8>` in C++.

Wouldn't it be `std::unique_ptr<std::vector<u8>>`?


Indeed, it would.


I agree that C++ broke the syntax terseness of C and become verbose for the sake of being explicit enough. This gets inconvenient sometimes, but the good thing is that it is a technical solvable problem - just redefine the long keywords or configure your editing tools to help you out typing.


It indicates ownership. If you know C++, ~T in Rust is similar to std::unique_ptr<T> in C++ (ensures that there is only one owner of given value) and @T is similar to std::shared_ptr<T> (ensures that there are one or more owners of given value and the value would be deallocated when the last owner vanishes). But you can't check the correctness of these "smart pointers" in the compile time in C++ while keeping the efficiency; Rust has both the correctness and efficiency (1) by making these important types to built-in constructs.

(1) Still being worked on.


Note that that loop function now looks like (the for loop has changed since 0.7):

    fn hexdigest(&self) -> ~str {
      let mut acc = ~"";
      for &byte in self.digest.iter() { 
        acc.push_str(fmt!("%02x", byte as uint));
      }
      acc
    }


just offhand, from my understanding, ~ signifies rust's pointer type. It's different from C's pointer type in subtle ways, so the symbol is different, to force reader to actually look into it, and understand what it does different instead of just assuming it's just like C's.

There's also the advantage that there's no way to conflict with the * multiplication operator.


`~` is just one pointer type; there's & and * also built-in to the language (and @, but that's moving to the standard lib); and Rust provides the control required for implementing arbitrary "smart-pointers" (e.g. reference counting is a library pointer).

~ is an owned pointer (like unique_ptr in C++); & is a "reference" or borrowed pointer (allowing access to data without taking ownership); * is a raw/unsafe pointer, and is identical to C's pointers.

Also, the * pointer doesn't conflict with multiplication because they can never occur in an ambiguous context:

  foo * bar = NULL; // C

  let bar: *Foo = std::ptr::null(); // Rust

  let bar = std::ptr::null(); // Rust, using type inference


Nothing in the syntax is fundamentally new, especially the semicolon turning an expression into a statement.

~[T] is just a vector.




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

Search: