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

#10

Doesn’t every function has its own type already? Like fn () {fn_name}

But currently there is no way to name it in code, so you cant add methods or impl traits for it. But what if we could?

I think it would be possible to name it just exactly like the function itself, if it is used in type’s context. Like this:

fn foo<T>() {}

fn main() {
    let f: foo<i32> = foo::<i32>;
}

This currently errors:

error[E0573]: expected type, found function `foo`
 --> src\lib.rs:4:12
  |
4 |     let f: foo<i32> = foo::<i32>;
  |            ^^^^^^^^ not a type

It works exactly the same way for structs, right?

struct Foo<T> {
    inner: T,
}

fn main() {
    let foo: Foo<i32> = Foo::<i32> {
        inner: 0,
    };
}

If this worked for fns, it would be possible to associate constants same way as for structs:

fn foo<T>() {}

impl foo<i32> {
    const FOO: i32 = 0;
}

trait HasConst {
    const CONST: i32;
}

impl<T> HasConst for foo<T> {
    const CONST: i32 = 123;
}

But I guess this would be a breaking change since this would forbid modules with same name as function.


#11

There’s a couple of proposed fixes for being unnable to name the function (disclaimer, both syntaces are mine):

  • Add the production like fn $path to the type grammar, that allows for explicitly naming function ZSTs. The space is obnoxious, since you’d have to write things like (fn foo)::CONST.
  • Add the production $expr::type to the type grammar, like a C++ decltype. This takes an expression, but instead of evaluating it, types it and returns the type.