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