Rust #5: Naming conventions
Tags: rust
Published: 2021-07-17 12:58:37

This week, I wanted to clarify in my head what the naming conventions are in the standard library if any, and to see if they help with communicating concepts in the API.

A good API has well-established concepts and paradigms and a good language to communicate that. When you see a certain proposition in a name or verb, you understand what that API is trying to do. Good examples are MacOSX's Cocoa API. Bad examples are the Windows Win32 API. For me, the most important thing is consistency.

I feel that Rust's `std` library API is trying to achieve the same thing, and I want to explore what that language is.

I have already seen some of this with the prefixes and suffixes of some methods of `Option` and `Result`, such as `or` and `or_else` and more simply the use of `is`.

Let us go through the tools that the `std` library uses to convey information to us.

## Casing

The Rust compiler is very opinionated about what casing and style you use to name things, even giving warnings when you break its rules. You can disable those warnings if you wish. Initially, I HATED the casing rules as I was used to my styles with my C++ background, and I have to admit, was a contention point for me in using Rust. But after a week of writing Rust code, I soon got used to it and appreciate there is consistency if not exactly what I'd prefer.

Here is a quick overview of the rules:

| Convention             | Types that use it                                                               |
|------------------------|---------------------------------------------------------------------------------|
| `snake_case`           | Crates, modules, functions, methods, local variables and parameters, lifetimes. |
| `CamelCase`            | Types (including traits and enums), type parameters in generics.                |
| `SCREAMING_SNAKE_CASE` | Constant and static variables.                                                  |

This means that when you have a type name and you want to refer to it in a function name, you have to convert. So `YourType` will become `your_type`. For example:

```rust
struct BankAccount {
    id: u64,
    name: String,
    amount: u128,
}

fn find_bank_account(id: u64) -> Rc<BankAccount> { ... }
```

Ok, this is general Rust stuff and not specific to the `std` library, but I thought it was worth going over.

## Basic types

The standard library naming convention documentation lists various pieces of text that are in method names based on the types that they operate on:

| Type name | Text in methods |
|-----------|-----------------|
| &T        | ref             |
| &mut T    | mut             |
| &[T]      | slice           |
| &mut [T]  | mut\_slice      |
| &[u8]     | bytes           |
| &str      | str             |
| *const T  | ptr             |
| *mut T    | mut_ptr         |

## Get and Set methods

Methods that fetch or set a value within their struct are usually called Getter and Setter methods.

For Setter methods, the prefix `set_` is used with the field name. For Getter methods, no prefix is used and just the field's name is. For example:

```rust
struct Person {
    name: String,
    age: u16,
}

impl Person {
    fn new(name: String, age: u16) -> Self {
        Person { name, age }
    }
    
    // Getters
    
    fn name(&self) -> &str { &self.name }
    fn age(&self) -> u16 { self.age }
    
    // Setters
    
    fn set_name(&mut self, name: &str) { self.name = name.to_string(); }
    fn set_age(&mut self, age: u16) { self.age = age; }
}
```

## Suffixes

Suffixes are pieces of text that appear at the end of a name. And the `std` library has a few. Below is a list I've discovered.

| Suffix    | Meaning                                                                          | Example               |
|-----------|----------------------------------------------------------------------------------|-----------------------|
| err       | Deals with the error part of a result                                            | Result::map\_err() |
| in        | This function uses a given allocator                                             | Vec::new\_in           |
| move      | Owned value version for a method that normally returns a referenced value        |                       |
| mut       | Mutable borrow version of method that normally returns owned or referenced value | &str::split\_at\_mut()  |
| ref       | Reference version for a method that normally returns an owned value              | Chunks::from\_ref()                      |
| unchecked | This function is unsafe - there may be dragons beyond!                           | &str::get\_unchecked() |

Due to the nature of Rust, many methods have variants. If a method `foo` returns an immutably borrowed value, then there are sometimes variants that return a mutably borrowed or an ownable copy. The suffixes `mut`, `move` and `ref` are used for this. For example:

```rust
fn foo() -> &T;             // original method
fn foo_mut() -> &mut T;     // mutable version
fn foo_move() -> T;         // owned version

fn bar() -> T;              // original version
fn bar_ref() -> &T;         // immutably borrowed version
fn bar_mut() -> &mut T;     // mutably borrowed version
```

However, there are exceptions. For conversion to an iterator returning owned values, `into_iter()` is used instead of `iter_move()`. Also, if the method contains a type name and it returns a mutable borrow, the `mut` appears before the type. For example:

```rust
fn as_bar(foo: &Foo) -> &Bar;
fn as_mut_bar(foo: &Foo) -> &mut Bar;
```

`err` is used with `Results` for methods that work with the error part instead of the OK part.

`unchecked` states that this method or function is unsafe. Make sure you add your protections around it. This suffix should be a BIG red flag to any user wanting to stay within Rust's safety. Usually, the documentation contains information on what you should do around this function to make sure it's still safe. For example, on `&str::get_unchecked(i)` the documentation asks that you:

- The starting index must not exceed the ending index.
- Indices must be within bounds of the original slice.
- Indices must lie on UTF-8 sequence boundaries.

Now, it may be that given index `i`, your program has already ensured that these conditions are true; in which case you can go ahead and use `get_unchecked` instead of `get` because it will be more efficient. However, as a programmer, whenever you see the `unchecked` suffix, you should be reaching for the documentation to see what those conditions are.

`in` is not stable yet and Standard Library methods that use this suffix are only in the nightly compiler as of the time of writing. This indicates a variant that allows the user to pass in their allocator for allocation. For example, whereas `Vec::new` creates a vector that will use the default allocator when adding elements, `Vec::new_in` requires an allocator object that implements the `Allocator` trait that will be responsible for allocations.

Occasionally, there are times where you want to have multiple suffixes.  If you do and one of them is `mut`, the convention is that `mut` should be last.

## Prefixes

| Prefix | Meaning                                                            | Example                 |
|--------|--------------------------------------------------------------------|-------------------------|
| as     | Free conversion from original                                      | &[u8]::as\_bytes()      |
| into   | Conversion that consumes original value                            | String::into\_bytes()   |
| is     | Query method that returns a bool                                   | Vec::is\_empty()        |
| new    | Indicates a constructor                                            | Box::new\_zeroed()       |
| to     | Expensive conversion from original                                 | &str::to\_owned()       |
| try    | This variant returns a Result instead panicking                    | Box::try\_new()          |
| with   | The variant uses a function to provide a value instead of directly | RefCell::replace\_with() |

`as`, `to` and `into` prefixes indicate conversions. But there are different types of conversions. There are conversions that are free because they are basically no-ops. There are conversions that are expensive because a new type is constructed from the old. And there are conversions that consume the original value to produce the new one. Rust differentiates between these conversions by using prefixes.

`as` essentially exposes a view to an underlying representation that is present in the original value. For example, a slice from a vector. `as` methods do not affect the original value and usually produce a borrowed reference. This means that the conversion's lifetime must be shorter than the original value.

`to` is an expensive conversion that constructs a new value from the old one. For example, creating a new `String` from a string slice. These methods usually produce an ownable value.

`into` can be expensive and can be a no-op depending on what it does. However, it consumes the original value. An example of this is converting a `String` to a `Vec<u8>` using `into_bytes()`. The `String` contains a `Vec<u8>` under the bonnet, and so producing it is effectively a no-op. But because it's given away the underlying structure, the `String` cannot exist anymore.

Another example is `into_iter()`, which consumes the original container and produces an iterator that can give back its values. It can do this because the new iterator value owns the values and not the original container.

These prefixes are essential in understanding the semantics of the operations their methods provide.

`new` is often the complete name of a static method for constructing an instance of a struct. However, sometimes there are different ways to do construction and therefore multiple variants of `new`. These all have the `new` prefix.

`try`, often used with `new` for the `try_new` prefix indicates a version of a method that can fail gracefully. For example:

```rust
// This function will panic if the id does not exist.
fn get_config(id: u32) -> Config { ... }

// This will not panic and will return a Result instead.
fn try_get_config(id: u3) -> Result<Config, ConfigError> { ...  }
```

If the `Result` version is the only version, then there is no need for the prefix `try`. It's only used for variants.

`with` indicates a method that whereas the original function passed a value as a parameter, this variant will be passed a function that provides that value instead. To be fair, I have not seen this suffix used that often. I have even seen other suffixes used to mean the same thing. For example, should `Option::ok_or_else` really be `Option::ok_or_with`?

## Conclusion

I found this research to be enlightening and helpful in understanding the Standard Library better.

Please comment below about any other prefixes or suffixes that I might have missed - I am sure that there are many.

Also, if you've been enjoying my series of random articles, please let me know what you want me to look into next.

Until next week!