Notes from golang dev learning to use Rust.
I started by watching this (protip: 1.25x speed is about normal), and following along in vscode (my usual IDE, lately).
If you're using vscode, you'll want the rust
and rust-analyser
extensions. At least as of 2020-08-13. rust-analyser
is being made more official, so at some point I expect it will be better utilized by the main rust
vscode extension.
If you want code formatting like gofmt
, that's rustfmt
and you'll need to install it manually by running rustup component add rustfmt
. Once you do that, vscode will automagically format your code when you save.
In Cargo.toml
, add the dependency under the [dependencies]
section. If you go to the top of a crate's page on crates.io
, there's a block that says, Cargo.toml
and has the line you'd need to put into your Cargo.toml
. There's even a button to copy it to your clipboard. There're more details on dependencies in Cargo.toml
, here.
You can use cargo-edit
. Once installed, the command will then be: cargo add <crate>
. That simply adds the specified dependency in the Cargo.toml
file with the current(?) version of the library.
How does this work compared with gomod
? With that there's the proxy that will cache modules, companies can have internal go proxies, modules get cached locally, etc. How does that compare with vendoring in Rust?
I was attempting to create a function that took instances of a type that implemented a specific trait (called a "trait object" in Rust parlance). I did a search for difference between golang interfaces and Rust traits and found this, which was useful.
The key part was the same simple example written in golang then in Rust.
Go:
type Foo interface { bar() }
func call_bar(value Foo) { value.bar() }
type X int;
type Y string;
func (X) bar() {}
func (Y) bar() {}
func main() {
call_bar(X(1))
call_bar(Y("foo"))
}
Rust:
trait Foo { fn bar(&self); }
impl Foo for int { fn bar(&self) {} }
impl Foo for String { fn bar(&self) {} }
fn call_bar<T: Foo>(value: T) { value.bar() }
fn main() {
call_bar(1i);
call_bar("foo".to_string());
}
(Bonus: I see that in Rust I can implement an interface on a type I didn't define.)
He goes on to talk about another way to do it in Rust, but doesn't include an example:
So far I've only demonstrated Rust having statically dispatched generics, but Rust can opt-in to the dynamic ones like Go (with essentially the same implementation), via trait objects. Notated like
&Foo
, which is a borrowed reference to an unknown type that implements theFoo
trait.
An example in A Quick Look at Trait Objects in Rust lead me to find that the same example using trait objects would be (or in the playground):
trait Foo { fn bar(&self); }
impl Foo for i32 { fn bar(&self) {} }
impl Foo for String { fn bar(&self) {} }
fn call_bar(value: &dyn Foo) { value.bar() }
fn main() {
call_bar(&1i32);
call_bar(&"foo".to_string());
}
There's a pretty complete writeup on Rust traits on the official blog, here. It's a good read.
Annoyingly, you cannot split a single Rust module across multiple files like you can with a Golang package.
Found these generic parameters with apostrophes (source):
pub struct EnumeratePixelsMut<'a, P: Pixel + 'a> where
<P as Pixel>::Subpixel: 'a, { /* fields omitted */ }
Apparently things like 'a
are lifetime annotations. A way to tell the Rust compiler how long-lived a value should be.
References:
- Lifetime Annotation Syntax (though I recommend reading the whole page to better understand what this is doing)
How to use a macro in a sibling module in the same crate. The key is the use of both
#[macro_export]
and#[macro_use]
. The order also also important themod
line decorated with#[macro_use]
must be before themod
line of the module where you want to use the macro. It was extremely frustrating to learn this.It was this that got me there, once I was able to ask the right question. And be sure to read the comments on the accepted answer.
In one file:
lib.rs
:In multiple files:
lib.rs
:a.rs
:b.rs
: