-
-
Save runexec/507f6e1617a08c613692 to your computer and use it in GitHub Desktop.
Revisions
-
oakes revised this gist
Apr 25, 2015 . 1 changed file with 3 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -255,7 +255,7 @@ fn main() { } ``` In that example, the `t` object is being completely replaced by a new one that uses UTC time instead of local time. Interestingly, the `say_something` function still cannot mutate it, because references are immutable by default as well. If we wanted to run the `clone_from` function there, we would have to use a mutable reference: ```rust // main.rs @@ -289,7 +289,7 @@ The neat thing about this is that you can tell when a function is mutating an ar In Clojure, we have the concept of `nil` to represent the lack of a value. It is convenient, but if we forget to check for it, we get the dreaded `NullPointerException`. Rust follows in the footsteps of languages like Haskell by doing the same thing it does with mutability: making it explicitly part of the type. For example, let's say we want the `say_something` function to let you pass a `Tm` reference _or_ nothing at all. If you do the latter, it will just create its own `Tm` object using `time::now_utc()`. To express this, we have to make it an optional type. That means changing the type to `Option<&time::Tm>` and changing the value we pass to it like this: ```rust // main.rs @@ -367,7 +367,7 @@ pub fn say_something(word: &str, t: Option<&time::Tm>) { } ``` Here, we are making the local variable `t_val`, which will contain either the value inside `t`, or a new object if `t` is `None`. Notice the `*` before `t_ptr`. This is doing the opposite of `&` by grabbing the value that the reference is referring to. We need to do this because `time::now_utc()` returns a value, and we need to make sure both return the same type. Also notice that neither expression in our `if` statement ends with a semicolon. Semicolons are used to demarcate statements. To return a value, we just write an expression without a semicolon. This is similar to what we do in Clojure. When we want to return a value, we simply put it at the end. Read [Expressions vs. Statements](http://doc.rust-lang.org/book/if.html#expressions-vs.-statements) to learn more. -
oakes revised this gist
Apr 25, 2015 . 1 changed file with 5 additions and 5 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -6,7 +6,7 @@ * [Modules](#modules) * [Crates](#crates) * [Types](#types) * [References](#references) * [Mutability](#mutability) * [Nullability](#nullability) * [Pattern Matching](#pattern-matching) @@ -199,13 +199,13 @@ Note that in this latest revision, we also moved the time object into a local va As the Tm docs indicate, the `asctime` function returns a [TmFmt](http://doc.rust-lang.org/time/time/struct.TmFmt.html). Although `println!` has no idea what that is, it doesn't matter. It implements a trait -- similar to a Clojure protocol -- called [Display](http://doc.rust-lang.org/std/fmt/trait.Display.html), which is all that `println!` needs. This mechanism is used pervasively in Rust. Read [Traits](http://doc.rust-lang.org/book/traits.html) to learn more. ## References The distinction in the previous section between stack and heap allocation is worth focusing on. In high-level languages, you can't control which is used, so you never think about it. In C and C++, you have complete control over it, but only at the price of being more error-prone. Rust promises to give you that control while being as safe as high-level languages. When you have direct control over memory allocation, you also have control over how values are passed to functions. In high-level languages, you normally just pass a value to a function and the language will decide whether to only pass a reference to the value or to pass an entire copy of the value. In Rust you explicitly pass references to values. That is what the `&` means in `&str`. Literal strings are automatically represented as a references, but under normal circumstances things will start their life as a value, and to pass them as a reference you will need to prepend them with `&`. For example, let's pass the `Tm` object to the `say_something` function: ```rust // main.rs @@ -232,7 +232,7 @@ pub fn say_something(word: &str, t: &time::Tm) { } ``` What would happen if we just did `say_something("Hello", t);`, and change the argument's type to `t: time::Tm`? The value `t` will be "moved" into the function, and will no longer be available outside of it. Since `say_something("Goodbye", t);` is called after, it will throw an error. Read [References and Borrowing](http://doc.rust-lang.org/book/references-and-borrowing.html) to learn more. ## Mutability -
oakes revised this gist
Feb 19, 2015 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -197,7 +197,7 @@ So, the syntax for arguments is similar to ML, where the name comes first, follo Note that in this latest revision, we also moved the time object into a local variable using `let`, which should be familiar to a Clojure user. In Rust, you are required to specify the types of top-level functions, but almost never for local variables. Rust has type inference, so it can figure out the type of `t` on its own. It happens to be [Tm](http://doc.rust-lang.org/time/time/struct.Tm.html). As the Tm docs indicate, the `asctime` function returns a [TmFmt](http://doc.rust-lang.org/time/time/struct.TmFmt.html). Although `println!` has no idea what that is, it doesn't matter. It implements a trait -- similar to a Clojure protocol -- called [Display](http://doc.rust-lang.org/std/fmt/trait.Display.html), which is all that `println!` needs. This mechanism is used pervasively in Rust. Read [Traits](http://doc.rust-lang.org/book/traits.html) to learn more. ## Pointers -
oakes revised this gist
Feb 12, 2015 . 1 changed file with 10 additions and 10 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -109,7 +109,7 @@ fn main() { } ``` Read [Crates and Modules](http://doc.rust-lang.org/book/crates-and-modules.html) to learn more. ## Crates @@ -162,7 +162,7 @@ pub fn say_goodbye() { } ``` Read [Crates and Modules](http://doc.rust-lang.org/book/crates-and-modules.html) to learn more. ## Types @@ -193,11 +193,11 @@ pub fn say_something(word: &str) { } ``` So, the syntax for arguments is similar to ML, where the name comes first, followed by a colon and then the type. In Rust, statically-allocated strings have the type of `&str`, which is pronounced as "string slice". Heap-allocated strings have the type of `String`. This is a distinction you don't find in Clojure or other high-level languages. Read [Strings](http://doc.rust-lang.org/book/strings.html) to learn more. Note that in this latest revision, we also moved the time object into a local variable using `let`, which should be familiar to a Clojure user. In Rust, you are required to specify the types of top-level functions, but almost never for local variables. Rust has type inference, so it can figure out the type of `t` on its own. It happens to be [Tm](http://doc.rust-lang.org/time/time/struct.Tm.html). As the Tm docs indicate, the `asctime` function returns a [TmFmt](http://doc.rust-lang.org/time/time/struct.TmFmt.html). Although `println!` has no idea what that is, it doesn't matter. It implements a trait -- similar to a Clojure protocol -- called `Show`, which is all that `println!` needs. This mechanism is used pervasively in Rust. Read [Traits](http://doc.rust-lang.org/book/traits.html) to learn more. ## Pointers @@ -232,7 +232,7 @@ pub fn say_something(word: &str, t: &time::Tm) { } ``` What would happen if we just did `say_something("Hello", t);`, and change the argument's type to `t: time::Tm`? The value `t` will be "moved" into the function, and will no longer be available outside of it. Since `say_something("Goodbye", t);` is called after, it will throw an error. Read [Pointers](http://doc.rust-lang.org/book/pointers.html) to learn more. ## Mutability @@ -326,7 +326,7 @@ This may seem like a lot of work compared to just using `nil`, but the advantage ## Pattern Matching In Clojure, we can get very powerful pattern matching capabilities using the [core.match](https://github.com/clojure/core.match) library. Rust has a similar mechanism baked into the language. This can be used to simplify complicated conditional statements using the `match` keyword. Read [Match](http://doc.rust-lang.org/book/match.html) to learn more. For our purposes, pattern matching can help us make our `if` statement safer. In the previous section, `say_something` is not very idiomatic, because it manually checks `t.is_some()` and calls `t.unwrap()`. It is much better to use the `if let` syntax like this: @@ -344,7 +344,7 @@ pub fn say_something(word: &str, t: Option<&time::Tm>) { } ``` Clojure, of course, has its own `if-let`, and the concept is very similar. The only difference is that we must use pattern matching to pull the value out of the option type. That's what `Some(t_ptr) = t` is doing. Pattern matching is used pervasively in Rust for everything from error handling to destructuring. Read [Patterns](http://doc.rust-lang.org/book/patterns.html) to learn more. ## Expressions @@ -369,7 +369,7 @@ pub fn say_something(word: &str, t: Option<&time::Tm>) { Here, we are making the local variable `t_val`, which will contain either the value inside `t`, or a new object if `t` is `None`. Notice the `*` before `t_ptr`. This is doing the opposite of `&` by grabbing the value that the pointer is pointing to. We need to do this because `time::now_utc()` returns a value, and we need to make sure both return the same type. Also notice that neither expression in our `if` statement ends with a semicolon. Semicolons are used to demarcate statements. To return a value, we just write an expression without a semicolon. This is similar to what we do in Clojure. When we want to return a value, we simply put it at the end. Read [Expressions vs. Statements](http://doc.rust-lang.org/book/if.html#expressions-vs.-statements) to learn more. Note that the same thing is done to return a value at the end of a function. If we wanted `say_something` to return our `Tm` object, all we need to do is indicate that in the type signature and then put `t_val` at the end of the function: @@ -393,8 +393,8 @@ pub fn say_something(word: &str, t: Option<&time::Tm>) -> time::Tm { You may have wondered this entire time why `println!` ends with a bang. In Clojure, it is idiomatic to do this for functions that are side-effecting. In Rust, it is the compiler-enforced syntax for macros. Users of Lisp dialects like Clojure are certainly fond of their macros, as there is a tremendous power, simplicity, and hubristic feeling of personal superiority they afford due to their homoiconic syntax. Rust is not homoiconic, and unsurprisingly the macro system isn't as powerful. Their primary purpose is similar to that of C macros: to reduce code duplication through symbol replacement. Unlike C macros, however, they are hygenic. Read [Macros](http://doc.rust-lang.org/book/macros.html) to learn more. If you are looking for the ability to run arbitrary code at compile-time, you may need to write a [compiler plugin](http://doc.rust-lang.org/book/plugins.html) instead. ## Learn More There is much more to learn about Rust from here. We haven't touched on lifetimes, the mechanism for achieving memory safety without garbage collection. We haven't looked at FFI, the mechanism for introducing segfaults and stack corruption into your program. The [Rust Book](http://doc.rust-lang.org/book/), which I've been linking to all along, is a great next step for the reader. -
oakes revised this gist
Jan 10, 2015 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -94,7 +94,7 @@ fn main() { Rust's `mod` is similar to Clojure's `ns` in that it creates a module, but they all go at the top of `main.rs` instead of in the files that the modules come from. From there, we can just prepend `utils::` to the function names to use them. Note that they are declared with `pub`. Unlike Clojure, Rust makes functions private by default. Rust's `use` is similar to Clojure's `require` in that it brings in an existing module. Here's a slightly modified `main.rs`, where we are bringing in symbols explicitly so we don't need to alias it, much like Clojure's `require` does with the `:refer` keyword: ```rust // main.rs -
oakes revised this gist
Jan 10, 2015 . 1 changed file with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -393,8 +393,8 @@ pub fn say_something(word: &str, t: Option<&time::Tm>) -> time::Tm { You may have wondered this entire time why `println!` ends with a bang. In Clojure, it is idiomatic to do this for functions that are side-effecting. In Rust, it is the compiler-enforced syntax for macros. Users of Lisp dialects like Clojure are certainly fond of their macros, as there is a tremendous power, simplicity, and hubristic feeling of personal superiority they afford due to their homoiconic syntax. Rust is not homoiconic, and unsurprisingly the macro system isn't as powerful. Their primary purpose is similar to that of C macros: to reduce code duplication through symbol replacement. Unlike C macros, however, they are hygenic. Read [Macros](http://doc.rust-lang.org/1.0.0-alpha/book/macros.html) to learn more. If you are looking for the ability to run arbitrary code at compile-time, you may need to write a [compiler plugin](http://doc.rust-lang.org/1.0.0-alpha/book/plugins.html) instead. ## Learn More There is much more to learn about Rust from here. We haven't touched on lifetimes, the mechanism for achieving memory safety without garbage collection. We haven't looked at FFI, the mechanism for introducing segfaults and stack corruption into your program. The [Rust Book](http://doc.rust-lang.org/1.0.0-alpha/book/), which I've been linking to all along, is a great next step for the reader. -
oakes revised this gist
Jan 10, 2015 . 1 changed file with 36 additions and 13 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -9,6 +9,7 @@ * [Pointers](#pointers) * [Mutability](#mutability) * [Nullability](#nullability) * [Pattern Matching](#pattern-matching) * [Expressions](#expressions) * [Macros](#macros) * [Learn More](#learn-more) @@ -108,7 +109,7 @@ fn main() { } ``` Read [Crates and Modules](http://doc.rust-lang.org/1.0.0-alpha/book/crates-and-modules.html) to learn more. ## Crates @@ -161,7 +162,7 @@ pub fn say_goodbye() { } ``` Read [Crates and Modules](http://doc.rust-lang.org/1.0.0-alpha/book/crates-and-modules.html) to learn more. ## Types @@ -192,11 +193,11 @@ pub fn say_something(word: &str) { } ``` So, the syntax for arguments is similar to ML, where the name comes first, followed by a colon and then the type. In Rust, statically-allocated strings have the type of `&str`, which is pronounced as "string slice". Heap-allocated strings have the type of `String`. This is a distinction you don't find in Clojure or other high-level languages. Read [Strings](http://doc.rust-lang.org/1.0.0-alpha/book/strings.html) to learn more. Note that in this latest revision, we also moved the time object into a local variable using `let`, which should be familiar to a Clojure user. In Rust, you are required to specify the types of top-level functions, but almost never for local variables. Rust has type inference, so it can figure out the type of `t` on its own. It happens to be [Tm](http://doc.rust-lang.org/time/time/struct.Tm.html). As the Tm docs indicate, the `asctime` function returns a [TmFmt](http://doc.rust-lang.org/time/time/struct.TmFmt.html). Although `println!` has no idea what that is, it doesn't matter. It implements a trait -- similar to a Clojure protocol -- called `Show`, which is all that `println!` needs. This mechanism is used pervasively in Rust. Read [Traits](http://doc.rust-lang.org/1.0.0-alpha/book/traits.html) to learn more. ## Pointers @@ -231,7 +232,7 @@ pub fn say_something(word: &str, t: &time::Tm) { } ``` What would happen if we just did `say_something("Hello", t);`, and change the argument's type to `t: time::Tm`? The value `t` will be "moved" into the function, and will no longer be available outside of it. Since `say_something("Goodbye", t);` is called after, it will throw an error. Read [Pointers](http://doc.rust-lang.org/1.0.0-alpha/book/pointers.html) to learn more. ## Mutability @@ -323,6 +324,28 @@ So, if we actually want to pass a value, we surround it with `Some(...)`, and if This may seem like a lot of work compared to just using `nil`, but the advantage is that `NullPointerException`s are impossible. We are forced by the compiler to check if it contains a value. Additionally, it has the same advantage that `&mut` has in the previous section; just by looking its type signature, we know which arguments allow no value to be passed. ## Pattern Matching In Clojure, we can get very powerful pattern matching capabilities using the [core.match](https://github.com/clojure/core.match) library. Rust has a similar mechanism baked into the language. This can be used to simplify complicated conditional statements using the `match` keyword. Read [Match](http://doc.rust-lang.org/1.0.0-alpha/book/match.html) to learn more. For our purposes, pattern matching can help us make our `if` statement safer. In the previous section, `say_something` is not very idiomatic, because it manually checks `t.is_some()` and calls `t.unwrap()`. It is much better to use the `if let` syntax like this: ```rust // utils.rs use time; pub fn say_something(word: &str, t: Option<&time::Tm>) { if let Some(t_ptr) = t { println!("{}, world at {}!", word, t_ptr.asctime()); } else { println!("{}, world at {}!", word, time::now_utc().asctime()); } } ``` Clojure, of course, has its own `if-let`, and the concept is very similar. The only difference is that we must use pattern matching to pull the value out of the option type. That's what `Some(t_ptr) = t` is doing. Pattern matching is used pervasively in Rust for everything from error handling to destructuring. Read [Patterns](http://doc.rust-lang.org/1.0.0-alpha/book/patterns.html) to learn more. ## Expressions In Clojure, everything is an expression, which means we can embed code inside of code without any restriction. In Rust, it's not _quite_ as pervasive, but nonetheless almost everything is an expression. The only things you've run into that can't be expressions are declarations, such as `mod`, `use`, `fn`, and `let`. @@ -335,18 +358,18 @@ What about `if` statements? In the previous section, `say_something` is delibera use time; pub fn say_something(word: &str, t: Option<&time::Tm>) { let t_val = if let Some(t_ptr) = t { *t_ptr } else { time::now_utc() }; println!("{}, world at {}!", word, t_val.asctime()); } ``` Here, we are making the local variable `t_val`, which will contain either the value inside `t`, or a new object if `t` is `None`. Notice the `*` before `t_ptr`. This is doing the opposite of `&` by grabbing the value that the pointer is pointing to. We need to do this because `time::now_utc()` returns a value, and we need to make sure both return the same type. Also notice that neither expression in our `if` statement ends with a semicolon. Semicolons are used to demarcate statements. To return a value, we just write an expression without a semicolon. This is similar to what we do in Clojure. When we want to return a value, we simply put it at the end. Read [Expressions vs. Statements](http://doc.rust-lang.org/1.0.0-alpha/book/if.html#expressions-vs.-statements) to learn more. Note that the same thing is done to return a value at the end of a function. If we wanted `say_something` to return our `Tm` object, all we need to do is indicate that in the type signature and then put `t_val` at the end of the function: @@ -356,8 +379,8 @@ Note that the same thing is done to return a value at the end of a function. If use time; pub fn say_something(word: &str, t: Option<&time::Tm>) -> time::Tm { let t_val = if let Some(t_ptr) = t { *t_ptr } else { time::now_utc() }; @@ -370,8 +393,8 @@ pub fn say_something(word: &str, t: Option<&time::Tm>) -> time::Tm { You may have wondered this entire time why `println!` ends with a bang. In Clojure, it is idiomatic to do this for functions that are side-effecting. In Rust, it is the compiler-enforced syntax for macros. Users of Lisp dialects like Clojure are certainly fond of their macros, as there is a tremendous power, simplicity, and hubristic feeling of personal superiority they afford due to their homoiconic syntax. Rust is not homoiconic, and unsurprisingly the macro system isn't as powerful. Their primary purpose is similar to that of C macros: to reduce code duplication through symbol replacement. Unlike C macros, however, they are hygenic. Read [Macros](http://doc.rust-lang.org/1.0.0-alpha/book/macros.html) to learn more. If you are looking for the ability to run arbitrary code at compile-time, you may need to write a [compiler plugin](http://doc.rust-lang.org/guide-plugin.html) instead. ## Learn More There is much more to learn about Rust from here. We haven't touched on lifetimes, the mechanism for achieving memory safety without garbage collection. We haven't looked at FFI, the mechanism for introducing segfaults and stack corruption into your program. The [Rust Guide](http://doc.rust-lang.org/guide.html), which I've been linking to all along, is a great next step for the reader. -
oakes revised this gist
Jan 9, 2015 . 1 changed file with 3 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -165,7 +165,7 @@ Read [Crates and Modules](http://doc.rust-lang.org/guide.html#crates-and-modules ## Types Until now, we've avoided seeing types because none of our functions take arguments. Rust is statically typed. The upside is, you will curse it at compile-time instead of at runtime. The downside is, "exploratory programming" means exploring how to convince the compiler to let you try an idea. Let's modify our functions so we pass the "Hello" or "Goodbye" as an argument: ```rust // main.rs @@ -231,7 +231,7 @@ pub fn say_something(word: &str, t: &time::Tm) { } ``` What would happen if we just did `say_something("Hello", t);`, and change the argument's type to `t: time::Tm`? The value `t` will be "moved" into the function, and will no longer be available outside of it. Since `say_something("Goodbye", t);` is called after, it will throw an error. Read [Pointers](http://doc.rust-lang.org/guide.html#pointers) to learn more. ## Mutability @@ -282,7 +282,7 @@ pub fn say_something(word: &str, t: &mut time::Tm) { } ``` The neat thing about this is that you can tell when a function is mutating an argument by simply looking at its type signature. If you don't see `&mut`, it can't do so (unless it's [internally mutable](http://doc.rust-lang.org/std/cell/)). It could still perform I/O like writing to the disk or requesting a network resource, so it's not necessarily pure in that sense, but at least we know that it's pure vis-à-vis its own arguments. ## Nullability -
oakes revised this gist
Jan 9, 2015 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -231,7 +231,7 @@ pub fn say_something(word: &str, t: &time::Tm) { } ``` What would happen if we just did `say_something("Hello", t);`, and change the argument's type to `t: time::Tm`? The value `t` will be "moved" into the function, and will no longer be available outside of it. Since `say_something("Goodbye", t);` is called after, it will throw an error. If `Tm` implemented the the [Copy](http://doc.rust-lang.org/std/marker/trait.Copy.html) trait, it would be copied into the function, which would work but may be inefficient. Read [Pointers](http://doc.rust-lang.org/guide.html#pointers) to learn more. ## Mutability -
oakes revised this gist
Jan 9, 2015 . 1 changed file with 3 additions and 3 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -63,7 +63,7 @@ fn main() { } ``` As you can see, the process is basically identical, and apart from syntactic differences, they both start you off with the same main function. With Cargo, if you leave out the "--bin", it will create a library instead. Either way, be sure to think hard about your project's name. A name like "Rust" ensures many opportunities for clever puns that will surely never get tiresome or old. ## Modules @@ -91,7 +91,7 @@ fn main() { } ``` Rust's `mod` is similar to Clojure's `ns` in that it creates a module, but they all go at the top of `main.rs` instead of in the files that the modules come from. From there, we can just prepend `utils::` to the function names to use them. Note that they are declared with `pub`. Unlike Clojure, Rust makes functions private by default. Rust's `use` is similar to Clojure's `require` in that it brings in an existing module. Here's a slightly modified `main.rs`, where we are bringing in symbols explicitly so we don't need to alias it, much like Clojure's `require` does with the `:as` keyword: @@ -114,7 +114,7 @@ Read [Crates and Modules](http://doc.rust-lang.org/guide.html#crates-and-modules As you know, languages with their own package managers usually have a special format for their libraries with their own unique name. Python has its "eggs" and Ruby has its "gems". Clojure has the disadvantage of being on an existing ecosystem, so it couldn't invent its own format; it uses the same boring "jars" as other JVM languages. Thankfully, Rust does not have this problem, and it chose to call its format "crates". This reflects the language's industrial roots and the humble, blue collar town of its sponsor: Mountain View, California. To use a crate, you add it to `Cargo.toml` much like you would with `project.clj`. Here's what mine looks like after adding the [time](https://crates.io/crates/time) crate: ```toml [package] -
oakes created this gist
Jan 8, 2015 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,377 @@ ## Contents * [Why care about Rust?](#why-care-about-rust) * [The Toolchain](#the-toolchain) * [Creating a New Project](#creating-a-new-project) * [Modules](#modules) * [Crates](#crates) * [Types](#types) * [Pointers](#pointers) * [Mutability](#mutability) * [Nullability](#nullability) * [Expressions](#expressions) * [Macros](#macros) * [Learn More](#learn-more) ## Why care about Rust? You already write software in Clojure. It pays your bills. You enjoy it. You're in an industry reaping disproportionate benefit from loose money policies, leading to a trend-chasing culture of overpaid nerds making web apps. You feel guilty about this, but there is nothing you can do about it because you have no other talents that a rational person would pay you for. Learning Rust will probably not do much to solve that problem for you. It won't assist you in making the ontological leap from a tired stereotype into something sentient and real. You will remain a replaceable silhouette with no discernible identity. It might even exacerbate the problem. However, it will give you a useful tool for writing low-level software. Let's start by reaffirming why we love Clojure: * Expressiveness (Lisp syntax, functional programming) * Interoperability (hosted on the JVM and JavaScript) * Concurrency (pmap/pvalues, atoms/agents/refs, core.async) Now let's think about Clojure’s weaknesses: * Performance (fast for a dynamic lang, but slow for a compiled lang) * Safety (would you program avionics or pacemakers in it?) * Embeddability (garbage collected, requires an external runtime) Rust’s strengths are Clojure’s weaknesses, and vice-versa. Rust isn’t as expressive or interoperable, and its concurrency story isn’t as complete. That said, it’s much better for performance or safety critical needs, and it can be embedded inside other programs or on very limited hardware. Many people try to compare Rust to Go, but this is flawed. Go is an ancient board game that emphasizes strategy. Rust is more appropriately compared to Chess, a board game focused on low-level tactics. Clojure, with its high-level purview, is a better analogy to the enduring game of stones. ## The Toolchain With Clojure, we typically start by installing [Leiningen](http://leiningen.org/), which builds projects and deploys libraries to [clojars.org](https://clojars.org/). The `project.clj` file at the root of a project specifies metadata like dependencies. One elegant aspect of Clojure we take for granted is that it is just a library, so projects can specify in this file what version of Clojure to use just like any other library. With Rust, we start by installing [Cargo](http://www.rust-lang.org/install.html), which builds projects and deploys libraries to [crates.io](https://crates.io/). The `Cargo.toml` file at the root of a project specifies metadata like dependencies. Rust takes the more traditional approach of being bundled with its build tool; it isn't a library and its compiler can't be embedded into programs for REPL-driven development. ## Creating a New Project With Clojure, we start an app with `lein new app hello-world`, which creates a project containing this: ```clojure (ns hello_world.core (:gen-class)) (defn -main "I don't do a whole lot ... yet." [& args] (println "Hello, World!")) ``` With Rust, we start an app with `cargo new hello_world --bin`, which creates a project containing this: ```rust fn main() { println!("Hello, world!"); } ``` As you can see, the process is basically identical, and apart from syntactic differences, they both start you off with the same main function. With Cargo, if you leave out the "--bin", it will create a library instead. Either way, be sure to think hard about your project's name. A name like "Rust" ensures many opportunities for clever puns that will absolutely never get tiresome or old. ## Modules While the Rust project doesn't start off with any equivalent to Clojure's namespace declaration, once you move beyond a single source file you'll need to use it. This isn't C or C++, where you just include files like a caveman. Rust separates code into modules, and each source file is automatically given one based on the file name. We can make a functions in a separate file like this: ```rust // utils.rs pub fn say_hello() { println!("Hello, world!"); } pub fn say_goodbye() { println!("Goodbye, world!"); } ``` ```rust // main.rs mod utils; fn main() { utils::say_hello(); utils::say_goodbye(); } ``` Rust's `mod` is similar to Clojure's `ns` in that it creates a module, but they all go at the top of `main.rs` instead of in the files that the modules come from. From there, we can just prepend `utils::` to the function names to use them. Note that they are declared with `pub`; unlike Clojure, Rust makes functions private by default. Rust's `use` is similar to Clojure's `require` in that it brings in an existing module. Here's a slightly modified `main.rs`, where we are bringing in symbols explicitly so we don't need to alias it, much like Clojure's `require` does with the `:as` keyword: ```rust // main.rs use utils::{say_hello, say_goodbye}; mod utils; fn main() { say_hello(); say_goodbye(); } ``` Read [Crates and Modules](http://doc.rust-lang.org/guide.html#crates-and-modules) to learn more. ## Crates As you know, languages with their own package managers usually have a special format for their libraries with their own unique name. Python has its "eggs" and Ruby has its "gems". Clojure has the disadvantage of being on an existing ecosystem, so it couldn't invent its own format; it uses the same boring "jars" as other JVM languages. Thankfully, Rust does not have this problem, and it chose to call its format "crates". This alludes to the industrial nature of the language and its humble blue collar origins in Mountain View, California. To use a crate, you add it to `Cargo.toml` much like you would with `project.clj`. Here's what mine looks like after adding the [time](https://crates.io/crates/time) crate: ```toml [package] name = "hello_world" version = "0.0.1" authors = ["oakes <[email protected]>"] [dependencies.time] time = "0.1.2" ``` To use it, we first need to declare the crate at the top of `main.rs`: ```rust // main.rs extern crate time; use utils::{say_hello, say_goodbye}; mod utils; fn main() { say_hello(); say_goodbye(); } ``` Then, in the file we want to use it in, we'll bring it in with `use`: ```rust // utils.rs use time; pub fn say_hello() { println!("Hello, world at {}!", time::now().asctime()); } pub fn say_goodbye() { println!("Goodbye, world at {}!", time::now().asctime()); } ``` Read [Crates and Modules](http://doc.rust-lang.org/guide.html#crates-and-modules) to learn more. ## Types Until now, we've avoided seeing types because none of our functions take arguments. Rust is statically typed. The upside is, you will curse it at compile-time instead of at runtime. The downside is, the notion of "exploratory programming" refers to you exploring how to convince the compiler to let you try an idea. Let's modify our functions so we pass the "Hello" or "Goodbye" as an argument: ```rust // main.rs extern crate time; use utils::say_something; mod utils; fn main() { say_something("Hello"); say_something("Goodbye"); } ``` ```rust // utils.rs use time; pub fn say_something(word: &str) { let t = time::now(); println!("{}, world at {}!", word, t.asctime()); } ``` So, the syntax for arguments is similar to ML, where the name comes first, followed by a colon and then the type. In Rust, statically-allocated strings have the type of `&str`, which is pronounced as "string slice". Heap-allocated strings have the type of `String`. This is a distinction you don't find in Clojure or other high-level languages. Read [Strings](http://doc.rust-lang.org/guide.html#strings) to learn more. Note that in this latest revision, we also moved the time object into a local variable using `let`, which should be familiar to a Clojure user. In Rust, you are required to specify the types of top-level functions, but almost never for local variables. Rust has type inference, so it can figure out the type of `t` on its own. It happens to be [Tm](http://doc.rust-lang.org/time/time/struct.Tm.html). As the Tm docs indicate, the `asctime` function returns a [TmFmt](http://doc.rust-lang.org/time/time/struct.TmFmt.html). Although `println!` has no idea what that is, it doesn't matter. It implements a trait -- similar to a Clojure protocol -- called `Show`, which is all that `println!` needs. This mechanism is used pervasively in Rust. Read [Traits](http://doc.rust-lang.org/guide.html#traits) to learn more. ## Pointers The distinction in the previous section between stack and heap allocation is worth focusing on. In high-level languages, you can't control which is used, so you never think about it. In C and C++, you have complete control over it, but only at the price of being more error-prone. Rust promises to give you that control while being as safe as high-level languages. When you have direct control over memory allocation, you also have control over how values are passed to functions. In high-level languages, you normally just pass a value to a function and the language will decide whether to only pass a reference to the value or to pass an entire copy of the value. In Rust, as in C and C++, you use pointers for the former. That is what the `&` means in `&str`. Literal strings are automatically represented as a pointer, but under normal circumstances things will start their life as a value, and to pass them as a pointer you will need to prepend them with `&`. For example, let's pass the `Tm` object to the `say_something` function: ```rust // main.rs extern crate time; use utils::say_something; mod utils; fn main() { let t = time::now(); say_something("Hello", &t); say_something("Goodbye", &t); } ``` ```rust // utils.rs use time; pub fn say_something(word: &str, t: &time::Tm) { println!("{}, world at {}!", word, t.asctime()); } ``` Note that we _could_ just do `say_something("Hello", t);` without the `&`, and change the argument's type to `t: time::Tm`. In that case, we would be passing `t` by value, which means it would make a complete copy of it in memory to give to the function. This is normally less efficient, unless you are just passing a small value like a number. Read [Pointers](http://doc.rust-lang.org/guide.html#pointers) to learn more. ## Mutability A Clojure programmer will be pleased to find that Rust shares a belief in data being immutable by default. The `Tm` object in the previous section cannot be mutated -- you'll get a compile error. For example, because it implements the [Clone](http://doc.rust-lang.org/nightly/core/clone/trait.Clone.html) trait, it has a function called `clone_from`, which lets you replace it with a completely new `Tm` object. This is obviously a mutation, so if we want to use it, we must declare it with `let mut`: ```rust // main.rs extern crate time; use utils::say_something; mod utils; fn main() { let mut t = time::now(); t.clone_from(&time::now_utc()); say_something("Hello", &t); say_something("Goodbye", &t); } ``` In that example, the `t` object is being completely replaced by a new one that uses UTC time instead of local time. Interestingly, the `say_something` function still cannot mutate it, because pointers are immutable by default as well. If we wanted to run the `clone_from` function there, we would have to use a mutable pointer: ```rust // main.rs extern crate time; use utils::say_something; mod utils; fn main() { let mut t = time::now(); say_something("Hello", &mut t); say_something("Goodbye", &mut t); } ``` ```rust // utils.rs use time; pub fn say_something(word: &str, t: &mut time::Tm) { t.clone_from(&time::now_utc()); println!("{}, world at {}!", word, t.asctime()); } ``` The neat thing about this is that you can tell when a function is mutating an argument by simply looking at its type signature. If you don't see `&mut`, it can't do so (unless it's a [mutable container](http://doc.rust-lang.org/std/cell/)). It could still perform I/O like writing to the disk or requesting a network resource, so it's not necessarily pure in that sense, but at least we know that it's pure vis-à-vis its own arguments. ## Nullability In Clojure, we have the concept of `nil` to represent the lack of a value. It is convenient, but if we forget to check for it, we get the dreaded `NullPointerException`. Rust follows in the footsteps of languages like Haskell by doing the same thing it does with mutability: making it explicitly part of the type. For example, let's say we want the `say_something` function to let you pass a `Tm` pointer _or_ nothing at all. If you do the latter, it will just create its own `Tm` object using `time::now_utc()`. To express this, we have to make it an optional type. That means changing the type to `Option<&time::Tm>` and changing the value we pass to it like this: ```rust // main.rs extern crate time; use utils::say_something; mod utils; fn main() { let t = time::now(); say_something("Hello", Some(&t)); say_something("Goodbye", None); } ``` ```rust // utils.rs use time; pub fn say_something(word: &str, t: Option<&time::Tm>) { if t.is_some() { println!("{}, world at {}!", word, t.unwrap().asctime()); } else { println!("{}, world at {}!", word, time::now_utc().asctime()); } } ``` So, if we actually want to pass a value, we surround it with `Some(...)`, and if we want to pass the equivalent of Clojure's `nil`, we pass in `None`. Then, in `say_something`, we can check if `t` contains a value using the `is_some` function, and if so, we call `unwrap` on it to get its value. This may seem like a lot of work compared to just using `nil`, but the advantage is that `NullPointerException`s are impossible. We are forced by the compiler to check if it contains a value. Additionally, it has the same advantage that `&mut` has in the previous section; just by looking its type signature, we know which arguments allow no value to be passed. ## Expressions In Clojure, everything is an expression, which means we can embed code inside of code without any restriction. In Rust, it's not _quite_ as pervasive, but nonetheless almost everything is an expression. The only things you've run into that can't be expressions are declarations, such as `mod`, `use`, `fn`, and `let`. What about `if` statements? In the previous section, `say_something` is deliberately verbose. There is clearly no benefit to writing redundant code like the calls to `println!` beyond ensuring one's own job security. In Rust, `if` statements are expressions, so we can just embed it into a `let` statement like this: ```rust // utils.rs use time; pub fn say_something(word: &str, t: Option<&time::Tm>) { let t_val = if t.is_some() { *t.unwrap() } else { time::now_utc() }; println!("{}, world at {}!", word, t_val.asctime()); } ``` Here, we are making the local variable `t_val`, which will contain either the value inside `t`, or a new object if `t` is `None`. Notice the `*` before `t.unwrap()`. This is doing the opposite of `&` by grabbing the value that the pointer is pointing to. We need to do this because `time::now_utc()` returns a value, and we need to make sure both return the same type. Also notice that neither expression in our `if` statement ends with a semicolon. Semicolons are used to demarcate statements. To return a value, we just write an expression without a semicolon. This is similar to what we do in Clojure. When we want to return a value, we simply put it at the end. Read [Expressions vs. Statements](http://doc.rust-lang.org/guide.html#expressions-vs.-statements) to learn more. Note that the same thing is done to return a value at the end of a function. If we wanted `say_something` to return our `Tm` object, all we need to do is indicate that in the type signature and then put `t_val` at the end of the function: ```rust // utils.rs use time; pub fn say_something(word: &str, t: Option<&time::Tm>) -> time::Tm { let t_val = if t.is_some() { *t.unwrap() } else { time::now_utc() }; println!("{}, world at {}!", word, t_val.asctime()); t_val } ``` ## Macros You may have wondered this entire time why `println!` ends with a bang. In Clojure, it is idiomatic to do this for functions that are side-effecting. In Rust, it is the compiler-enforced syntax for macros. Users of Lisp dialects like Clojure are certainly fond of their macros, as there is a tremendous power, simplicity, and hubristic feeling of personal superiority they afford due to their homoiconic syntax. Rust is not homoiconic, and unsurprisingly the macro system isn't as powerful. Their primary purpose is similar to that of C macros: to reduce code duplication through symbol replacement. Unlike C macros, however, they are hygenic. Read [Macros](http://doc.rust-lang.org/guide.html#macros) to learn more. If you are looking for the ability to run arbitrary code at compile-time, you may need to write a [compiler plugin](http://doc.rust-lang.org/guide-plugin.html) instead. ## Learn More There is much more to learn about Rust from here. We haven't touched on lifetimes, the mechanism for achieving memory safety without garbage collection. We haven't looked at pattern matching, the mechanism for destructuring collections and simplifying conditionals. We haven't looked at FFI, the mechanism for introducing segfaults and stack corruption into your program. The [Rust Guide](http://doc.rust-lang.org/guide.html), which I've been linking to all along, is a great next step for the reader.