Idea: associated items for functions


#1

This is something I’ve wanted a lot. It would help when a function returns some type unique to that function, eg. when a function has it’s own error type:

fn eat_snacks() -> Result<(), self::eat_snacks::Error> {
    pub enum Error { .. };
    ...
}

It could also help when a function uses constants that you may want to be visible outside the function:

fn request_info_from_peers() {
    pub const NUM_REQUESTS_IN_PARALLEL: usize = 23;
    ...
}

#[test]
fn test_request_info_from_peers() {
    for i in 0..request_info_from_peers::NUM_REQUESTS_IN_PARALLEL {
        ...
    }
    ...
}

You get the idea. Is this something that anyone else wants?


Those-Which-Must-Not-Be-Named (i.e., everything we can't name)
#2

I know that I’d like this.

The problem is that a function currently defines neither a type nor a namespace.

fn     f() {}
mod    f   {}
fn     F() {}
struct F   {}

This is valid today, and would have to become invalid if functions would be a type and/or namespace. This is common today: tuple structs define a type and a function.


#3

We could just allow both at the same time. eg. this is valid:

mod foo {
    pub struct MyThing;
}

fn foo() {
    pub struct MyOtherThing;
}

But this is invalid:

mod foo {
    pub struct MyThing;
}

fn foo() {
    pub struct MyThing; // error: `MyThing` defined twice
}

It’s not super-elegant, but I don’t know if it would really cause problems in practice.


#4

This sounds like it’s just sugar for a mod foo { pub fn ... } and a use foo::*;. Is that correct? If not, what’s the difference?


#5

@Ixrec I’m not exactly sure what you’re describing there. But yes, it’s possible to do this just with modules. eg.

fn foo() {
    pub struct Bar;
    ...
}

is the same as

mod foo {
    pub struct Bar;
}

fn foo() {
    use self::foo::*;
    ...
}

This feature wouldn’t let you do anything new and powerful. It’s just more convenient syntax.


#6

One reason I want this is so that I can have the docs for my function also display the consts/types which are only relevant to that function, rather than making the user navigate to a module with the same name.

Also, we let types double as modules via inherent impls. So I don’t see why we can’t do the same thing for functions.


#7

Name collisions in rust today are easily described in terms of two (or three) namespaces:

  • modules/types
  • values
  • (and maybe macros)

To extend @CAD97’s example with a few more cases:

// Currently allowed
fn     f() {}
mod    f   {}
macro_rules! f {}

// Currently allowed
fn     F() {}
struct F   {}
macro_rules! F {}

// Currently forbidden!
mod   x {}
struct x {}

// Currently forbidden!
const X: i32 = 0;
fn    X() {}

ISTM that allowing fns to double as modules requires either:

  • Making fns define modules under certain conditions (a breaking change if those conditions do not require novel syntax or attributes)
  • Adding a fallback mechanism to path resolution. shudder

#8

How about the following syntax?

// `fn` is in a function as `Self` is in an `impl`
// this lines up with the idea that 'fn would be the
// "fucntion body region/lifetime", as has been proposed elsewhere
fn eat_snacks() -> Result<(), fn::Error> {
    pub enum Error { .. };
    ...
}

const MY_ERROR: fn eat_snacks::Error = ..

// or, if it's in an impl,
const MY_ERROR: MyStruct::fn eat_snacks::Error = ..

AFAICT there should be no parsing ambiguities, since fn in non-item position must be followed by ( currently (playground). That said, while I feel vaguely grossed out by the space in fn foo appearing in a path, this is already present in fn() -> T (though I think you might need to include parens for that). We could go with (fn eat_snacks)::Error but I don’t like that either.


#9

Random thought: Another advantage to making every function/method it’s own type would be so that you can refer to them in trait bounds. eg. When I implement a trait, I should be able to relax the effects on methods like so:

struct MyConstEqType;

// Make `eq` a const fn, even though it's non-const in the trait definition.
impl PartialEq for MyConstEqType {
    const fn eq(&self, other: &MyConstEqType) -> bool {
        true
    }
}

I could then refer to types that have const equality by referring to the eq method:

fn takes_a_const_eq_argument<T>(val: T)
where
    T: PartialEq,
    T::eq: ConstFn(&T, &T) -> bool
{
   ...
}