Language feature: named returns
Optionally name the return types, as follows:
fn some_fn(a: A, b: B) -> (_a, _c): (A, C) {
// ...
let xxx: A = ... ;
let yyy: C = ... ;
(xxx, yyy)
}
or perhaps, even:
fn some_fn(a: A, b: B) -> (_a: A, _c: C) {
// ...
let xxx: A = ... ;
let yyy: C = ... ;
(xxx, yyy)
}
A few observations:
- Increase in verbosity is always a struggle, but there is a chance for function signatures to convey meaningful information, if needed.
- In this case the tuple return, the right-side (with cosmetic field naming)
-> (_a: A, _c: C), actually looks visually similar to the left-side(a: A, b: B) ->. So I don't think this optional syntax addition looks THAT alien. - I expect that no function's body would need any change, whether adding or removing the return's naming information.
- Otherwise, perhaps
_aand_ccould be actually declared at the top of the function. But I personally dislike the Go's way, and I think that if_aand_cwere indeed automatically declared and used in the function's body, the return would still need to be an explicit(_a, _c)-like expression.- But if, for example,
_awere to be mutable, then I guess the signature would have to be-> (mut _a: A, _c: C)instead ;x - I'm not a macro spellcaster but I think this could be possible with macros and/or with attributes on functions.
- But if, for example,
- Otherwise, perhaps
This idea is based on ADA's in/in_out/out characteristics of parameters. From the little I read about it, each of their (function-like) procedures' outputs is just a normal (named) parameter tagged as out. If Rust were to incorporate that, I think the change would be the addition of names for the return type, since Rust already places the "out" parameters at the right-side of ->.
For example, the HashMap's insert could, instead of:
pub fn insert(&mut self, k: K, v: V) -> Option<V>
be:
pub fn insert(&mut self, k: K, v: V) -> _old_value: Option<V>
(I think the documentation keeps being necessary, as it is today, in this particular case)
I think some functions may have longer names in the absence of this cosmetic feature. Let's say..
pub fn insert_and_also_returns_the_old_value(&mut self, k: K, v: V) -> Option<V>
/exaggeration
Today, in most cases, the return type is implicitly "named" after the function's name itself. eg. the is_xxx() functions means that the return type (which is a boolean) could also be named "is_xxx".
But some functions' returns are not named after the function's name itself. For those cases, the return could be treated as a parameter which is "write-only" (as ADA's out parameters), and for being a parameter, could enjoy having a name for maintainability/documentation purposes.
This is essentially covered by #2584.
I see, the usage of structural records as return types? On a first thought, it would be ambiguous, I suppose:
pub fn insert(&mut self, k: K, v: V) -> {old_value: Option<V>} {
// ...
}
I'm afraid there is some syntax ambiguity ({}{}) and it would also require a change in the functions' bodies , since they would need to cope with that exact anonymous type, wouldn't it?
I'd expect it to be written like:
pub fn insert(&mut self, k: K, v: V) -> struct {old_value: Option<V>}
There's no ambiguity, you have to write -> { old_value: Option<V>, } { ... }.
@swfsql Where would you expect the name to be seen? Why is a name in the syntax better than just having it in the summary first sentence in the documentation?
(With @Centril's anonymous structs you'd have to say it to use it, which I don't think it the right answer in general as part of the function for the same reason people don't return 1-tuples usually.)
I do like structural records but I now see them as partially just a shortcoming of associated items and/or the documentation system.
Imagine you could write:
pub fn insert(&mut self, k: K, v: V) -> insert::Output {
pub struct Output { old_value: Option<V> }
...
where fn_name::Output is special item that all functions posses, but gets presented in the functions docs if it's defined as a new item. An even narrower approach goes like
#[doc(present_inside=insert)]
pub struct InsertOutput { old_value: Option<V> }
pub fn insert(&mut self, k: K, v: V) -> InsertOutput {
...
In either case, if fn_name::Output becomes a thing then you can destructure by writing
let insert::Output { old_value } = tbl.insert(k,v);
Yes these schemes both feel slightly hackish but if they avoid enough complexity by presenting tricky ideas in terms of simpler ideas then that's acceptable. There is no complexity around attributes or traits in either of these schemes for example.
@scottmcm I expect the name to be used implicitly on the function body. I think that the documentation regarding the output is still needed, but to have named outputs on a function's signature makes it more explicit, which I consider to be a gain.
I have added in the issue the actual reference for that feature: in ADA, output parameters are named and it makes sense for me.
Regarding @Centril 's structural records, I agree, the returns' type(s) would actually change, and the syntax [for the output parameters] would get away from that one of the input parameters. So even considering some sugaring, I don't think it covers this feature (named returns).
@burdges I personally liked this concept! The destructuring and the actual syntax seems familiar, as you said.
Although I see these two downsides: (1) the repetition of the function's name, as in insert::Output or InsertOutput, which would be mostly needed; and (2) the possibility for such return structure, which is "very attached" to that particular function, to be defined in unusual places, such as (a) anywhere in the function's body, or (b) anywhere in the outer scope.
(regarding (2)): I think, since it is a form of "[output] parameter", I would prefer all functions to have such return type a fixed [definition] place, but that would seem too hacky-sh.
Or to put it from another perspective, a reader wouldn't get much information by seeing self_function::Output in the signatures, so it would be, in practice, considered overhead.
Although not so much for named return's case, I find this ("functions implying modules situation") interesting.
(This is a off-topic to this feature request, and is just a note):
If both of these features were added - the named return and the optional/alternative tuple syntax - then this would open a possibility to "kind of" consider both function's inputs and outputs as tuples, and this could open up optional function syntax.
This would mean that a function always have a single tuple as input, and always have a single tuple as output (excluding the -> impl Traitand -> ! cases).
fn fun(a: A, b: C) -> (x: X, y: Y) { .. } would mean there is a tuple as input, and a tuple as output.
fn fun(a: A, b: C) { .. } would be equivalent to
fn fun(a: A, b: C) -> () { .. } - as of today,
fn fun -> (x: X, y: Y) { .. } would be equivalent to
fn fun() -> (x: X, y: Y) { .. }, and
fn fun { .. } would be equivalent to
fn fun() -> () { .. }.
But then, it would look quite alien to the C syntax
Although I see these two downsides: (1) the repetition of the function's name, as in
insert::OutputorInsertOutput
Yes, but all alternatives add syntactic complexity and cost more "strangeness budget". I think structural records for which default traits presents issues, maybe no worse than tuple, but maybe unsolvable. And other schemes look like roughly:
pub fn insert(&mut self, k: K, v: V) -> #[derive(Clone)] pub struct Output { old_value: Option<V> }
{
impl Iterator for insert::Output { ... } // Can you actually avoid insert::Output?
...
(2) the possibility for such return structure, which is "very attached" to that particular function, to be defined in unusual places, such as (a) anywhere in the function's body, or (b) anywhere in the outer scope.
It'll often make more sense to define these complex return types towards the end of the function, much like the builder pattern often benefits from builder functions being written before their outputs.
We've largely forgotten the lessons of literate programming because we render docs with only hypertext navigation. An actual sensible presentation requires authors have some flexibility in ordering.
As an aside, I'd think proc macro should be sufficiently powerful for named returns, perhaps even Go-like, so
#[named_return]
fn fun(a: A, b: C) -> (x: X, y: Y) { .. }
adds a return (x,y); at the end and replaces all return; with (x,y) everywhere.
I've uploaded a draft proc-macro which implements some of the suggested functionality (currently only the initial declaration):
- https://crates.io/crates/named_return
But I couldn't make it work with proc-macro-attr. I'm not sure but I think Rust parses the "original syntax" before the proc-macro-attr executes.. rendering it invalid syntax.
Would you know if there is a workaround?
Today, i was writing rust after a long time, some basic math functions like below. Then suddenly i realised it won't work. Please make them work!
fn direct_prop(x1: f32, y1: f32, x2: f32) -> y2: f32 {
(y1 / x1) * x2
}
fn inverse_prop(x1: f32, y1: f32, x2: f32) -> y2: f32 {
(y1 * x1) / x2
}