Can I put a block after the -> and write any arbitrary function?
What's the motivation? Are you just wanting to not write the type, or are you trying to not write the {}s, are you wishing rustfmt put them on one line, or …?
Responding to your second type, an explicit return type is a written contract of the values that the function will return. Inferring return types opens the door to a lot of complexity
That said, we are open to proposals that involve new syntax
And personally, I do hope that we add a syntax like this someday, for many different reasons but especially because it would provide a great way to write things like fn func(...) -> Result<T> = try { ... } without having to introduce more special syntax for such functions.
I expect that it'll take some long conversations and careful consideration, but it certainly hasn't been ruled out.
Agreed, but there's one circumstance where inferring return types would not break this property: when the body of the function already names the type at the top level, and we're doing exclusively local reasoning of propagating that named return type to the function signature (and documenting it in the rustdoc for that function).
For instance, if you have fn new(x: u64) -> Self { Self { x } }, the return type is already named in the body, and it'd be perfectly reasonable to write fn new(x: u64) = Self { x }.
On the other hand, I don't think it's reasonable to infer a return type if you write fn func() = otherfunc(), because that would rely on non-local inference, and because that would not allow type inference to stop at function bodies (making it less scalable, both for the compiler and for humans reading the code).
If we're making a simplified syntax for putting functions on one line, I'd personally get more use out of something like this for making trait implementations easier:
pub struct Foo { .. }
impl Foo {
pub const fn new() -> Self { .. }
}
impl Default for Foo {
// Declares that `Foo::default()` has the same parameters, return type,
// and body as `Foo::new`.
//
// Equivalent to `fn default() -> Self { Self::new() }`.
fn default = Self::new;
}
I write this exact setup often because I want types that can be constructed in a const context, but also implement Default, and this is the best way to ensure that both options exist and are in-sync with each other.
As another upside, this avoids having to deal with type inference, since it can just copy the return type from the referenced function.
There are Expression-bodied members in C# and I find them really nice. It is one of those small features that are not essential but very pleasant to use. Like ? operator.
fn from(inner: u8) => Self(inner)
instead of
fn from(inner: u8) -> Self {
Self(inner)
}
Or
pub fn descr(&self) -> format!(
"Some very long description ({}): {}"
self.xyz
.map(|val| some_mapping(val + 1))
.unwrap_or_default(),
self.abc,
);
This syntax is technically tricky to parse — it puts types and expressions in the same position, so it requires parsing ahead to find ; or { after the type-or-expression, and then backtrack and re-parse it as the correct item. Rust had to give up on type ascription syntax, because distinguishing between types and expressions is tricky.
There have been proposals to use = before the body, which would be much easier to parse.
But I don't think that's a change worthy of making. Rust supports closures which already take care of majority of trivial functions. Rust also supports implicit return of the last expression in a block, which makes 1-liner functions a bit smaller too. There isn't much boilerplate left to remove, and IMHO it's not worth complicating the syntax to save a couple of chars in a rare case.
Potentially off-topic, but I find the "Read-only properties" C# has more interesting, more fitting for such simple functions and more useful for Rust in general (as they're more flexible), especially when combined with traits.
I assume most of these short functions we're talking about exist on a struct and not standalone.
Consider the following:
// Perhaps even allowing to derive traits that only contain fields that match
// #[derive(MyAbstraction)]
pub struct MyStruct {
my_private_field: usize,
pub my_public_field: usize,
// Could potentially even allow making them read/get-only:
// pub(super=get) my_public_field: usize,
}
impl MyStruct {
// Note: Implicit &self/&mut/self self may not be easy/possible/make sense
pub get name_of_old_field = self.my_private_field * 2;
pub set name_of_old_field(value: usize) = {self.my_private_field = value/2};
}
pub trait MyAbstraction {
// Associated types (exist already)
// type Something;
// Functions/Methods (exist already)
// fn do_something(&self) -> usize;
// Field accesses (implemented like functions, but not requiring brackets)
// Not sure what a good syntax would be, may need to be like
// fn (&self argument), but there may need to be a difference to functions due
// to Rust allowing functions with the same name as fields.
get my_public_field(&self): usize;
get extra_field(&self): String;
}
impl MyAbstraction for MyStruct {
// No need to list my_public_field if a corresponding field exists already.
get extra_field(&self) = "Hello World";
}
fn run(s: impl MyAbstraction) {
// No brackets needed
let x: String = s.extra_field.toString();
// Currently not possible through a trait unless this field is added
// through a function.
let y: usize = s.my_public_field;
}
fn run2(s: MyStruct) {
s.name_of_old_field = 6;
dbg!(s.name_of_old_field);
}
This would allow the following:
Implementations could change their memory layout (even if they have public fields), or even make formerly public fields no longer exist without a breaking change.
Can indicate that a field is easy/cheap to access, currently as_* methods are used for that.
Generics and impl Trait can access fields and not just methods/functions.
As far as I can tell that would also cover most situations where you'd want shorter function implementations, with the added benefit of not requiring brackets when they're called (which could be useful to indicate they are cheap to call.
One problem is of course that it can hide complexity and overhead from executing the getter, another that it could start a war over when to use functions and when not.
Though there are probably a variety of reasons I'm not thinking of why code that is executed should be called with brackets (besides the naming collision between fields and functions),
There's much more helpful in C#, where it saves the return.
Even there, though, I'm often torn whether it's worth it because it adds another meaningless choice when writing code, and another place where when you make something slightly more complex you have to then redo the syntax to a different form.
Also C#'s => doesn't change the type annotation requirements. So the equivalent in Rust to C#'s
int foo() => 4;
would be just
fn foo() -> i32 => 4;
which is exactly the same length as
fn foo() -> i32 { 4 }
anyway.
So I've always been skeptical of something like expression-bodied members in Rust.
In particular, are they worth bothering with over just using more "shortness"/"simplicity" heuristics in rustfmt? We didn't need "expression-bodied if blocks" to put short ifs on one line...
I think the cases you listed are different problems that would benefit more from a dedicated syntax.
With constructors, writing {} is the least of the problems. The big pain is in repeating most fields 3 times — in struct definitions, in argument definitions, and in the struct literal. This proposal doesn't meaningfully change the boilerplate. For constructors I'd prefer something removing the field name and type repetition.
With trait implementations, there's a big jump in boilerplate when you switch from derive to a custom implementation. We've got #[default] for that reason, and such annotations could be extended to some hypothetical #[default = Self::new]. It's also painful to exclude a field from Ord or Debug, and again this proposal only removes two characters from multiple lines of boilerplate.
You definitely made a lot of valid points. The feature is generally a minor syntax sugar and without return type inference, it becomes quite pointless.