rust :: vs .

·

3 min read

When reading rust codes, you sometimes see File::open or f.read(). Why are some functions accessed with ::, and others with .?

By taking a look at the source code for std::fs::File, we could see that these functions are defined either in impl File, or impl SomeTrait for File:

// src/std/fs.rs snippet
impl File {
    pub fn open(path: P) -> io::Result<File> {snip}
    pub fn create(path: P) -> io::Result<File> {snip}
    pub fn create_new(path: P) -> io::Result<File> {snip}

    pub fn metadata(&self) -> io::Result<Metadata> {snip}
}
impl Read for File {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {snip}
}
impl Write for File {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {snip}
}

Either impl File or impl SomeTrait for File adds functions to the File struct, but why are they accessed differently?

When to use ::

  1. accessing a struct's associated functions

  2. accessing a crate or a module's functions. we'll come back to this later

What's an associated function?

An associated function is a function that operates on the struct. For example, both File::open() and File::create() return a new File instance (technically a File instance wrapped in a Result enum). Some languages call these functions static methods.

Associated functions are always accessed with a ::.

The most common example is the new() function that creates a new instance and initializes its member values:

// A struct about Cats.
struct Cat {
    name: String,
    age: u8,
}

impl Cat {
    // An associated function to create a new cat.
    fn new() -> Cat {
        Cat{
            name: String::from("notadog"),
            age: 0,
        }
    }
}

fn main() {
    // Call the associated function to birth a new cat instance.
    let mycat = Cat::new();
}

Placing functions in impl Cat or impl SomeTrait for Cat makes obvious to anyone reading the code the relationship between the function and Cat.

When to use .

On the other hand, . is how you access instance methods. These methods operate on the instance (or object). These methods' first argument is always self. This is identical to many other languages that support object-oriented programming.

Examples include f.read(), f.write():

use std::fs::File;
let f = File::open("myfile.txt");
f.read(); // <-- calling `read` method on instance `f`

Another scenario that uses :: is to access items in a crate or module. For example, io.copy() does not belong to any struct, it's a function defined in the std::io module.

Take a look at the source code for io::copy(), you'll see copy.rs module located under src/std/io:

// src/std/io/copy.rs snippet
pub fn copy<R: ?Sized, W: ?Sized>(reader: &mut R, writer: &mut W) -> Result<u64>

To access copy(), you'd need to use std::io; or use std::io::copy; to bring it into scope:

use std::io;
io::copy(r, w);

// or

use std::io::copy;
copy(r, w);

As you can see, the use keyword also uses :: to locate io module in std crate. Here :: behaves almost like / in a path.