Idea: associated items for functions

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?

2 Likes

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.

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.

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?

4 Likes

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

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.

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
3 Likes

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.

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

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.

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.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.