This might be premature, but I've been thinking about a general output syntax for gen
/try
-style functions. Just wanting to put these ideas out there.
General Syntax
Broadly, the arrow syntax (-> T
) would indicate type-wise output in general, rather than exclusively the return
type. Output types can be introduced by a keyword to specify which output they refer to. As usual, the return
type would be introduced without a keyword; defaulting to unit if unspecified.
For gen
functions, the yielded output type would be introduced by the keyword yield
.
gen fn all_ints() -> yield u64 {
for i in 0.. {
yield i;
}
}
For try
functions, the error output type would be introduced by the keyword throw
.
try fn assert_nonzero(input: i32) -> throw Error {
if input == 0 {
throw Error::Zero;
}
}
To specify multiple output types, the output can instead be a semicolon-separated sequence of output types; wrapped in curly brackets. The general return
type is the last type in the sequence if it isn't introduced by a keyword. Bracketing is required as it avoids ambiguity with associated items in traits.
try fn validate(input: String) -> { throw Error; String } {
if input == "bad" {
throw Error::BadInput;
}
input
}
try gen fn function(input: i32) -> {
throw Error;
yield State;
i32
} {
assert_nonzero(input)?;
yield State::Pending;
input - 1
}
Yielded types must be specified for gen
functions, and error types must be specified for try
functions. If the function isn't introduced by the respective keyword, the respective output type can't be specified.
This helps to prevent confusion from syntax such as gen fn function() -> T
, where otherwise the reader may assume that T
is the yield
type (when it's actually the return
type).
Syntax (Functions - The Rust Reference)
...
FunctionReturnType :
->
(OutputType | OutputTypeList)OutputType :
Type |
yield
Type |throw
TypeOutputTypeList :
{
OutputType (;
OutputType)*;
?}
Advantages
-
Requires no extra keywords (like plural forms -
throws
,yields
). -
Easily extended to any similar future possibilities.
-
Unambiguous to parse and read.
- Types like
fn(T) -> U
andimpl Fn(T) -> U
require no parentheses. - Output types are clearly delineated, unlike:
try fn validate() throws Error -> String
("throws error to string"?). - The open bracket draws attention to output broken onto multiple lines, which may be common.
- Reads as a type-wise complement to the function body (e.g.
{ yield A; B }
to{ yield a; b }
).
- Types like
Disadvantages
-
If bracketing is forgotten for the output of a required function in a trait, the latter output types would be evaluated as associated items.
This would hopefully always error, but it could leave the door open for future associated items with type-ambiguous syntax to cause confusion.
fn function() -> yield A; B; // `B` is evaluated as an associated item.
-
Limits the ability to use
{ .. }
as syntax for a type.Anonymous structs for example; although those could, and probably should, be introduced with a keyword (e.g.
struct { .. }
). -
Readers might confuse a function's bracketed output for something else.
-
It could be confused for a type, but this is unlikely since
{ .. }
isn't used for any existing type. -
It could be confused for the function's body, but this is unlikely since the output should generally be small and involve Pascal-case types.
-
-
Using the same keyword is less "greppable" - looking for value-wise usage of
throw
oryield
would turn up semi-false positives. Functions that throw or yield would still be searchable usinggen
andtry
.
Alternatives
-
The intuitive syntax for
gen
andtry
functions is usually provided asgen fn f() yields Y -> R
andtry fn f() throws E -> R
, using the keywordsyields
andthrows
.The intention of the syntax proposed in this post is to be a less ambiguous and more extensible form of the same idea.
-
Could avoid bracketing by disambiguating output types from associated items by keyword, but this would be more fragile to parse and ambiguous to read:
gen fn f() -> yield Y; R;
. -
Could use a different bracketing style.
(yield A; B)
,[yield A; B]
- May be confused for a type, particularly a tuple or array.<yield A; B>
- May be confused for generic parameters.- Some other token, like how closures use
|
for their parameter list.
-
Could use a different separator between output types.
,
- May read like a keyword applies to each of the following types (yield A, B
).|
- May appear like an anonymous sum type syntax (yield A | B
).:
,=>
,>>
- May read like the output type evaluates to the next type in some way (yield A => B
).
This would also hurt the ability to read the output "as a type-wise complement to the function body".