Pre-RFC use function name as the type of the function


#1

If we have a function foo this would have foo as the name of it’s type. This would allow us to name things that we can’t currently name, one example of this is Map<slice::Iter<T>, foo> for the result of calling map(foo) on a slice::Iter<T> where foo is a function taking &T. To me it is quite intuitive what foo means here (I actually tried doing this when I was learning rust).

This would not be a backwards compatible change. However because functions use snake_case and types use CamelCase I don’t think we would have a lot of breakage. Currently you would have to have a function and a type with the same name and ignore the lint that says you do not follow the naming convention for this change to break anything.

The only big things that we still can’t directly name with this change are closures, return types of functions with impl Trait, and in the future return types of async functions. A lot of times a closure can be changed to a function (if it does not capture anything) and impl Trait and async will be namable using existential types. Another benefit of this is that we can immediately get the output of foo using foo::Ouput because it implements the Fn traits. This is another problem that came up with async functions.

An instance where this would be useful is a struct where it’s Iterators can be expresed as abother iterator with some functions called on in. This is why I chose Map as the example above, you could have some struct Iter { inner: Map</**/, foo> } and implement traits by delegating.


#2

An edge case: a tuple-struct definition defines both a type and a corresponding constructor function with the same name. Under this proposal, the type of constructor function would be unnameable I think?

Example: https://play.rust-lang.org/?gist=3a98cb6ca3226c69ade8a165b63e1e48&version=stable&mode=debug&edition=2015


#3

An alternative that is not subject to @matklad’s problem is fn name, where name is more generally a (possibly UFCS) qualified path:

type Iter<'a, T> =
    std::iter::Map<
        std::slice::Iter<'a>,
        fn <T as Trait<A>>::method<B>,
    >;

Parentheses are not allowed to appear at the beginning of a path so I don’t think this is ambiguous. (It does give fn two different meanings in types though)


My guess though is that since named existentials are planned, it’s better to double down on that feature than to provide multiple ways of achieving the same thing.


#4

A more general feature that side-steps all of the problems that have been identified (and more) is typeof <expr>.


#5

Maybe it is implemented as a function but I would consider the constructor to be something else. While the tuple struct uses () other structs use {} which differentiate them from other functions.

struct Foo(i32);
struct Bar { data: i32 }

fn main() {
    let x = Foo(0);
    let y = Bar { data: 0 };
}

Besides using this proposal if you really need to name a function that creates a struct you could always use something like fn foo(data: i32) -> Foo { Foo(data) }.

@ExpHP I would also be happy with fn foo however I don’t really see a reason to have the additional fn because we don’t do something like struct name or enum name.


#6

++, this is what I’ve suggested elsewhere and I think it side-steps all the annoying problems; I do think you want fn <T as Trait<A>>::method::<B> though… right? (never mind, I worked out a contrived example demonstrating you’re right).

trait Foo<T> {
    const K: T;
}

fn foo<T: Default>() {}

impl<T> Foo<T> for fn foo<T> where T: Default {
  const K = T::default(); // yeah it's not const just humor me
}

const FOO_I32: i32 = fn foo<i32>::K; // once the parser hits a `fn` token 
                                     // it's no longer in expr mode

I think I also suggested at some point that within a function’s scope (including paramteres and return type), it should be allowed to abbreviate as fn or similar (I imagine that some might want fn to be like self and refer to the unique value of the function’s associated ZST, but writing something like fn.type would be ok, too).

I agree that we should have some mechanism like this, but I’m opposed to the keyword typeof; I’m a bigger fan of val.type or val::type, since it avoids introducing a new keyword, and because Rust seems to me much more comfortable with method chaining than C++ is. See also Java, Scala, and Kotlin’s respective T.class, x.type, and x::class. Certainly, I think that C++ spelling it decltype(x) was a mistake.

I also really hate multi-word keywords, but that’s just me.

In fact, there’s talk floated about of making Bar's ctor be a function as well, by allowing f { .. } syntax for function calls. Not sure where all that went though. I think it’s a nice feature that a tuple struct exposes its constructor as a bona-fide function, and changing this would produce a lot of churn anyways. Plus, types and values (which include functions) live in separate namespaces, and attempting to unify those is very much a “this kills the crab” scenario.

Also, something I’d really like is fn foo::return as sugar for <fn foo as FnOnce<..>>::Output (well, we can completely side-step the trait system, the desugaring is just for illustration) and maybe its friends like fn foo::yield.


#7

Why would you use foo::return when we already have Output from the Fn traits? I much prefer foo::Output which would be a side effect of this proposal.


#8

In order to get at the FnOnce::Output type you have to cast the type <_ as FnOnce(..) -> _>::Output. The Output associated type belongs to the trait, not the type itself. So just making the function type namable is not enough by itself.

You can try this yourself with some feature flags: create a struct, impl FnOnce, and try to get the Output associated type.


#9

The problem is, this will be a breaking change. Currently functions do not preserve its name in type context. This means that function and module with same name can exist in same scope. So the code below is a totally valid rust code.

pub fn foo() -> SomeType{
    ...
}

pub mod foo {
    pub type Output = SomeOtherType;
}

So auto-declare function’s name as type name will be breaking change. Though in rust syntax level breakage like this is allowed between edition change, I don’t think this proposal is significant enough to justify it.

Suggestion: How about fn::func_name? It surely is a syntactic hack, but dudes in JS world found it’s useful :wink:


#10

See type TypeOfFoo = fn foo; above.


#11

Since breaking changes are on the table, has anyone looked into how much of a breaking change this would be? Are there many crates out there that have functions and modules with the same name? How would we want rustfix to handle this?