To define-then-use or not, that is the question

So, I thought a nice goal for 2026 was to finally get to know rust better and I'm doing quite okay I think. Since I want to do rust and not my-usual-mash-up-of-languages in rust I thought I'd ask your opinions.

In some languages you absolutely have to define your items first (as in, above the first usage) with some minor exceptions (e.g. ocaml) and in others you can access them as long as their "associated value" is defined before usage (e.g. js). In rust it seems you can use whatever is statically know (?).

So compare the following two examples and tell me which one you think is more idiomatic:

impl Foo for Bar {
  fn f() -> Y {
    fn some_complex_predicate(x: &X) -> bool;
    fn some_specific_function(x: &X) -> Y;
  
    match foo {
      A(x) if some_complex_predicate(&x) => some_specific_function(x)
    }
  }
}

which forces a reader to at least skim over some code that isn't immediately relevant

or

impl Foo for Bar {
  fn f() -> Y {
    return match foo {
      A(x) if some_complex_predicate(&x) => some_specific_function(x)
    };
  
    fn some_complex_predicate(x: &X) -> bool;
    fn some_specific_function(x: &X) -> Y;
  }
}

which uses early returns to sort of mimic haskells where. If rust didn't have where as a keyword with similar semantics already I'd pick the first option, but since it does... what's your opinion here?

Note: please keep in mind that I just started with rust a week ago after reading the book, rust by example and a bit of the nomicon. Maybe I'm missing some crucial piece to e.g. write "open traits" which would allow defining the functions outside of f.

1 Like

Apart from the point that Rust's where is only for trait bounds, not local definitions like Haskell's, the problem with your second example is that, people who have worked for a good amount of time, now instinctively read code after return as unreachable. Your helper definitions will trigger a mental "wait, why is this here?" reaction. But I have a personal preference that may get similar reactions too (see below) :'D

From what I have seen, people use one of the following:

impl Foo for Bar {
    fn f() -> Y {
        match foo {
            A(x) if some_complex_predicate(&x) => some_specific_function(x),
        }
    }
}

fn some_complex_predicate(x: &X) -> bool { ... }
fn some_specific_function(x: &X) -> Y { ... }

Closures (for short, context-capturing logic)

impl Foo for Bar {
    fn f() -> Y {
        let threshold = compute_threshold();
        let some_complex_predicate = |x: &X| -> bool { x.value > threshold };
        let some_specific_function = |x: &X| -> Y { ... };

        match foo {
            A(x) if some_complex_predicate(&x) => some_specific_function(x),
        }
    }
}

I really dislike free functions, just a personal thing and it it's not a common pattern in Rust codebases. So I usually just make lots of Util unit structs for module level helpers for namespacing. And often it is helpful, because I can export them to other files if needed as a struct (I mean you can still do that with a module directly, which is what most people do, but like I said, personal preference :'D)

impl Foo for Bar {
    fn f() -> Y {
        match foo {
            A(x) if FooUtils::some_complex_predicate(&x) => FooUtils::some_specific_function(x),
        }
    }
}

struct FooUtils;

impl FooUtils {
    fn some_complex_predicate(x: &X) -> bool { ... }
    fn some_specific_function(x: &X) -> Y { ... }
}

Nested function items are harder to test, which is probably one of the reasons why people don't use that pattern.

The first version is better because it doesn't require escaping the function using a return which makes for a more natural control flow.

Just put the functions outside of the trait impl:

impl Foo for Bar {
    fn f() -> Y { ... }
}
fn some_complex_predicate(x: &X) -> bool { ... }
fn some_specific_function(x: &X) -> Y { ... }

or you can put them as Bar's methods:

impl Bar {
    fn some_complex_predicate(x: &X) -> bool { ... }
    fn some_specific_function(x: &X) -> Y { ... }
}

The Rust namespacing mechanism you're looking for is a module:

impl Foo for Bar {
    fn f() -> Y {
        match foo {
            A(x) if utils::some_complex_predicate(&x) => utils::some_specific_function(x),
        }
    }
}

mod utils {
    pub fn some_complex_predicate(x: &X) -> bool { ... }
    pub fn some_specific_function(x: &X) -> Y { ... }
}
4 Likes

I know, that most people do that, but I just like structs UpperCamelCase :'D

If all you want is to name the module using this non-standard naming convention you can do that, no need for the struct:

#![allow(non_snake_case)]

mod FooUtils {   
}
1 Like

That's just cheating :'D

The rust stdlib places definitions first (at least at the example I found - I wouldn't expect a strong convention on this particular style question):

pub fn read_to_string<P: AsRef<Path>>(path: P) -> io::Result<String> {
    fn inner(path: &Path) -> io::Result<String> {
        let mut file = File::open(path)?;
        let size = file.metadata().map(|m| usize::try_from(m.len()).unwrap_or(usize::MAX)).ok();
        let mut string = String::new();
        string.try_reserve_exact(size.unwrap_or(0))?;
        io::default_read_to_string(&mut file, &mut string, size)?;
        Ok(string)
    }
    inner(path.as_ref())
}

https://doc.rust-lang.org/src/std/fs.rs.html#346-356

1 Like