Skip to content

Instantly share code, notes, and snippets.

@dcormier
Last active August 23, 2020 20:47
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dcormier/475b6f16944d7d11d796f28bd418d992 to your computer and use it in GitHub Desktop.
Save dcormier/475b6f16944d7d11d796f28bd418d992 to your computer and use it in GitHub Desktop.
On Rusting

On Rusting

Notes from golang dev learning to use Rust.

Beginning

I started by watching this (protip: 1.25x speed is about normal), and following along in vscode (my usual IDE, lately).

Visual Studio Code

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.

Common References

Adding Dependencies

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.

Vendoring Dependencies

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?

Using Golang Interfaces vs Rust Traits

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 the Foo 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.

Golang Packages vs Rust Modules

Annoyingly, you cannot split a single Rust module across multiple files like you can with a Golang package.

Apostrophes In Generic Parameters

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:

@dcormier
Copy link
Author

dcormier commented Aug 23, 2020

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 the mod line decorated with #[macro_use] must be before the mod 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:

// Note that this module, decorated with macro_use MUST be defined BEFORE mod b,
// where the macro is used.
#[macro_use]
mod a {
    pub fn foo() {}

    #[macro_export]
    macro_rules! bar {
        () => {};
    }
}

mod b {
    fn baz() {
        crate::a::foo();
        bar!();
    }
}

In multiple files:

lib.rs:

// Note that the order of those lines is important! If you were to do this,
// calling the macro from mod b would not work.
//
// mod b;
// #[macro_use]
// mod a;

#[macro_use]
mod a;
mod b;

a.rs:

pub fn foo() {}

#[macro_export]
macro_rules! bar {
    () => {};
}

b.rs:

fn baz() {
    crate::one::foo();
    bar!();
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment