Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

free-function

Level: warn · Article: Method Ownership

A free function almost always has a home. Either in its return type or its primary parameter.

What it checks

A free function (not in an impl) whose first parameter is one of your own types, or which returns one of your own types. main, extern functions and generic parameters are excluded.

Don’t

#![allow(unused)]
fn main() {
struct User {
    banned: bool,
    name: UserName,
}

struct Url(String);

fn ban(user: &mut User) {
    user.banned = true;
}

fn parse_url(s: &str) -> Result<Url, ParseError> {
    Ok(Url(s.to_string()))
}

fn format_user(user: &User) -> String {
    format!("{}", user.name)
}
}

Six months later somebody who could not find ban adds a second one on UserService. Now there are two, and one is wrong.

Do

#![allow(unused)]
fn main() {
struct User {
    banned: bool,
    name: UserName,
}

impl User {
    fn ban(&mut self) {
        self.banned = true;
    }

    fn display_name(&self) -> String {
        format!("{}", self.name)
    }
}

struct Url(String);

impl Url {
    fn parse(s: &str) -> Result<Self, ParseError> {
        Ok(Self(s.to_string()))
    }
}
}

user.ban(). Url::parse(s). One place to look, one place to add logic, and the compiler knows the method exists so nobody writes it twice.

Silence it

#![allow(unused)]
fn main() {
// rabot: allow(free-function) spans two types and belongs to neither: the transaction orchestrates both
fn commit(store: &Store, orders: &[Order]) -> Result<(), CommitError> { .. }
}

The article’s exceptions: stateless math (clamp), top-level orchestration (App), and operations that genuinely belong to a third thing.