I realized this scenario is currently kind of awkward in Roc syntax:
match paths.first() {
Ok(path) => File.delete!(path)?
_ => {}
}
basically the situation where you want to pattern match on a tag union, extract a named value from it, and then run an effect that ends up with you having {} - and then otherwise doing nothing
what if we allowed this instead?
if Ok(path) = paths.first() {
File.delete!(path)?
}
(this is essentially if let in Rust but without the let)
Sounds great! I don’t like that it increases the surface area of the language, but it seems well worth it.
As long as we don't add matches!
I really hate that some people use if let and other use if matches
Hmm
Actually, I don't like the syntax. Too easy to make a bug.
Richard Feldman said:
what if we allowed this instead?
if Ok(path) = paths.first() { File.delete!(path)? }
That might be a bug....the user might actually want:
if Ok(path) == paths.first() {
File.delete!(path)?
}
That should be caught by shadowing warnings
I wonder if this has been brought up before: https://cse.hkust.edu.hk/~parreaux/papers/ultimate-conditional-syntax-ml22/
It does propose to combine pattern matching and conditionals which has been suggested and rejected many times here, but it would solve this particular issue rather nicely without introducing a third bespoke control flow construct to the language
If they put a single equals sign, then sign can't already be defined
If they put double equals, it's already defined
Sam Mohr said:
That should be caught by shadowing warnings
Unless the variable has an _ after it. Or if a new user is confused and just assumes they need to add an underscore after to remove the warning
Yeah, well
I think that's a somewhat minor case, and I think newcomers that don't add the underscore initially will read the warning that said "add an underscore to make it reassignable"
I think it would always be a compile error if you meant to use ==
Probably won't be a common error, but I bet there will be users that go from:
path = "..."
if Ok(path) = paths.first() {
...
}
To
path_ = "..."
if Ok(path_) = paths.first() {
...
}
Which is still broken. Feels like a really easy thing for a new user to accidentally do
really? :thinking:
why would someone try to write that?
I can see mixing up = and == (and getting a compile error) but I don't follow why someone would attempt to do that, even by mistake :big_smile:
oh wait I understand
you're saying they wrote Ok(path) = when they should have written Ok(path) == in that last example
but we can do a more specific error if they do shadowing inside an if pattern
like "hey are you sure you didn't mean to do == here?"
also note that the more likely beginner mistake would be the opposite order: if list.first() = Ok(path) just because that's the order people usually write things like that in
and that would obviously be an error (which also could give a custom helpful message)
Good point
Also, would this allow for any sort of pattern matching?
if [x, y, z] = my_list {
...
}
yeah totally
as long as it's exhaustive
actually nm wouldn't need to be in that case I guess
since it's if and not match
If we support this, I think we should maybe not support ?? return val
Since:
?? way feels like a hack to me since the purpose of ?? is to provide a default valueI definitely think we should support both
It is really the same as regular ? to me
Like even with pattern matching if we will still want ?
But it is technically redundant
Maybe ?? using question marks is confusing, then, since they both handle Results but very differently IMO
? does early return unconditionally
?? does no early return, unless the thing passed to it returns in its expression
I think the reason we even suggested allowing ?? to support return was because we had suggested some pre-braces version of if-let that got rejected, so the ?? return val was a simple stopgap that worked for Result and no other union types
Actually, I may be wrong
The x = y ?? return val desugars to:
x = match y {
Ok(ok) -> y
Err(_) -> return val
}
Which would need the equivalent of let-else, not if-let
yeah
if wouldn't be convenient for that use case
AKA
Ok(x) = y else {
return 5;
}
I guess if we want return x to be an expr anyway, then so long as we don't support let-else then we still maintain basically one way to do things
So I think ?? return val is okay, and we probably shouldn't add let-else if it stays
But I think let-else is more communicative, and ?? doesn't work for non-Results, not true for let-else
yeah I'm not a fan of else without if
We already have if sans else, I'm not seeing why this is much different
The other thing we brought up last time was that the above example makes its control flow known at the end of the line
But the same problem applies to ?? return val
It follows an expression with no preface
So if we need to pick a different word, that's okay with me. But ?? return not handling non-Results and not actually providing a default value for a Result is a strong pair of detriments in my eyes that don't exist in let-elsecatch|orelse|fail|fall|etc.
we already have foo()? with control flow at the end of the line
Sam Mohr said:
We already have
ifsanselse, I'm not seeing why this is much different
you mean not seeing why else sans if is different?
Sam Mohr said:
We already have
ifsanselse
We do?
Richard Feldman said:
we already have
foo()?with control flow at the end of the line
Yes, which is why I think there's no advantage in this aspect for ?? vs. let-else
Brendan Hansknecht said:
Sam Mohr said:
We already have
ifsanselseWe do?
Two places, on already agreed on, one planned but not fleshed out:
if cannot_continue { return early }styles = [if hovered { Color("red") }, Size(16->px)]Oh, I read that backwards
I was thinking else sans if
Yep, that doesn't exist elsewhere
I proposed it for the behavior that Richard pushed should instead be what the ? binop is today
Aka try-else in this proposal I wrote a while back: https://docs.google.com/document/d/1pBNytZYF5aOCYgmHno2Y-8im-tWfbLLYN5RIu8dEe6s/edit?usp=sharing
So Richard has now made his if-less else dislike known twice
But I'm still making known my thought that it seems to work pretty well in Rust and have fewer downsides than ?? return
I don't understand the downside of ?? return
it isn't a special-case haha
it's just a natural consequence of what ?? desugars to
we'd have to create a special rule to disallow it if we didn't want it to exist, right?
because it's ?? <expr> and return ____ is an expr
I think it's because I'm thinking long-term, we don't want error messages for ?? to say "this match statement has problems"
Meaning I'd want to "desugar" it post-typechecking, as we were doing in the Rust-based compiler for the ? suffix
We want to be able to say "this thing should be a Result" for values before ? or ?? for max friendliness
And so, in the non-desugaring world, I think the special-case isn't there
I guess we already expect people to consider the desugared code when writing binops since that's somewhat required to understand how + and - interact with methods
But still, I think expecting users to think about how something desugars is smelly to me
It's no longer "use ?? to provide lazy defaults for Results", it's "use ?? as a desugaring to a match expression that handles Results"
It's hard for me to understand what's best for the average developer here, since I know how all this stuff works, having worked on this part of the compiler a good deal
All in all, I definitely think ?? return works
It just goes weakly against the "small set of orthogonal primitives" cardinal guiding rule
Since you can't do Admin(admin) = user ?? return NotAdmin
You need to now break out a whole match statement
But with some let-else equivalent, you get Result and non-Result handling with the same feature
everything Just Works:tm:
hm, I don't follow
I totally get the point about the compiler wanting to special-case it in order to give more helpful error messages, but I think that's good compiler design and not part of the rules of the language
the "small set of simple primitives" goal is about the rules of the language
and that's the part where ?? <expr> implies that ?? return ____ should work
and having one more (of many others!) compiler special-cases to improve error message quality is unrelated to that
Yeahhhhhh... I think even with the compiler not desugaring ?? to a match and instead making it a proper IR node, we'd have to actively disallow the use of return there if return is an expression
So if return is an expression, my point is moot
I think that return only would make sense as an expression and not a statement if we need it for ??, right?
I think supporting val.method(arg1, return other) would be a weird side effect
So if it's an expression and not a statement, we'd have to give warnings anywhere it's used within another expression
Except for ?? and brace-less if
But brace-less if is already a warning that we format away
So now, talking through this, it seems like we are supporting "return as an expression" to support ?? and actively warning about "return expression" used elsewhere, but pushing against other solutions because we have "return as an expression" already
So if there's another place we need "return as an expression", then again I can shut my trap
@Sven van Caem just letting you know I've looked at the paper you linked on UCS. It's an interesting concept that I think could marry well with our plans for union refinement, but I'm not sure how understandable this would be to beginners since UCS doesn't "read like English" in the way if-else and when-is do... I'll have to mull this over
Return expressions likely could be used in some if or match expressions that not only early return but also return a value in other cases.
So I don't think it is ?? specific
return has to be an expression for this to work:
answer = if a { do_something(b) } else { return "blah" }
:point_up: yeah, exactly that
same with having it in match arms
because an expression is the thing that comes after else
Technically it doesn’t have to be an Expr in that example if we say that returns can end a block but then there isn’t much difference
But return in many many many Expr positions is ridiculous, but I guess that is fine for the purposes of parsing
Recently I read Vine docs, found a bit awkward yet interesting take on the related syntax (also the implication operator):
https://vine.dev/docs/features/conditions#the-is-operator
Speaking of roc,
Does this look weird?
if paths.first() is Ok(path) {
File.delete!(path)?
}
Does this look even weirder?
if paths.first() is not Ok(_) {
return 42
}
Btw why x ?? y and not x.with_default(y)?
I guess x ?? y was introduced because x |> Result.with_default y was noisy and inconvenient. But with static dispatch it doesn't look that bad, right? It's even more expressive imo
Kiryl Dziamura said:
Btw why
x ?? yand notx.with_default(y)?
This is a really solid question now that we have static dispatch
it's just much more concise haha
Sure, but with default really isn't that common. So is it special enough to deserve ??
It isn't uncommon, but I don't think it is particularly special. Like if we had static dispatch first we might not have ever considered adding ??
:thinking: I wonder if there's a more concise name that would close the gap
Also, it's likely you won't use ?? in chaining because it would require parens around the expression. So sometimes you would fallback to the chaining syntax. It makes ?? convenient only in specific cases
x ?? y
x.ok_or(y)
x.if_err(y)
x.or_else(y)
x.with_default(y)
ok_or is pretty close
x.or(Ok(y))? -> x.or_ok(y)? :big_smile:
or is a reserved keyword at the moment
Speaking of naming. I hate the word “unwrap” but at least “unwrap_or” expresses what happens with the value in opposite to “with_default”. Just a note
or is reserved
Yeah, I was joking. That kind of syntax would mess with the return type anyway
so I guess the three options would be:
path = paths.first() ?? return ""
Ok(path) = paths.first() else return ""
if Ok(path) = paths.first() {
...
}
aesthetically I still don't like using else like this, but if we were to get rid of ??, it does have the objective advantage of being more flexible because it works on non-Result patterns too
Kiryl Dziamura said:
Recently I read Vine docs, found a bit awkward yet interesting take on the related syntax (also the implication operator):
https://vine.dev/docs/features/conditions#the-is-operator
Speaking of roc,
Does this look weird?
if paths.first() is Ok(path) { File.delete!(path)? }Does this look even weirder?
if paths.first() is not Ok(_) { return 42 }
I’m still wondering what do you guys think about this :grinning_face_with_smiling_eyes:
It's not ideal, but it eliminates both the = vs == problem and let else
I’m not sure how often negative partial matching is needed tho
I think that's more confusing than = vs ==
because a lot of languages use is as an expression
in some cases it even means the same thing as ==
so in those languages, if paths.first() is Ok(path) { would mean the same thing as if paths.first() == Ok(path) {
I don't know of any languages that have = and == doing the same thing :big_smile:
Oh, now I see the intention of the first two options:
path = paths.first() ?? return ""
Ok(path) = paths.first() else return ""
I don't like them because they unexpectedly mix two statements, but they don't increase the indentation.
how would you prefer to write this?
I definitely don't like how this reads:
Ok(path) = paths.first() else return ""
I definitely read it as:
Ok(path) = (paths.first() else return "")
Can ?? be used only for the early return? The question marks suggests it in some sense. It might be implied as parametric unwrap where both the expression and the return value are not necessarily Results.
When you don't need early return - use .with_default
Ok(path) = paths.first() ?? "" # return empty string if not Ok
# desugars to
match paths.first() {
Ok(path) => # continuation
_ => ""
}
It would work only with incomplete pattern matching: pat = expr ?? expr.
However, early return for the operator is likely uncommon in other languages. Maybe other options that involve a question mark?
We could support let-else with
unless Admin(admin) = user {
return fail
}
I don't expect it's needed often so the verbosity is not a problem. Also I like how it complements the if case
if !(Admin(admin) = user) {
return fail
}
Hmmm
That probably looks a bit confusing, but unless feels quite unnecessary to me
I feel like something more built into if would make more senses here.
Oh, though scoping is different than if.....hmm
Its stolen from Ruby
ah
The problem with:
if !(Admin(admin) = user) {
return fail
}
and
unless Admin(admin) = user {
return fail
}
Is that most often you would want to early return if the condition is not met, but continue and use the unwrapped value if the condition is met.
Looking at this, it doesn't seem like the admin should be in scope after the if/unless block.
But if admin is available only in the body of the if then it is useless, because you enter the block only when the condition is not met, and so admin would not be bound to anything.
Aaaa, so with unless the admin binding would be available after the block.
Seems confusing to me, but I never used ruby...
yes, it kinda reminds the mind bending around backpassing. but I like the unless proposal anyway :blush:
I'd expect the admin variable to be in scope for the rest of the code following the closing brace
Kiryl Dziamura said:
Can
??be used only for the early return? The question marks suggests it in some sense. It might be implied as parametric unwrap where both the expression and the return value are not necessarilyResults.When you don't need early return - use
.with_defaultOk(path) = paths.first() ?? "" # return empty string if not Ok # desugars to match paths.first() { Ok(path) => # continuation _ => "" }
I think at that point I'd probably expect it to just work like infix ? does today except without the lambda, so both of these would do the same thing:
path = paths.first() ? |_| ""
path = paths.first() ?? ""
in that world, the scenarios are:
.ok_or?Err and return: infix ???I'm just lurking here, but I like the idea of something like unless. Its similar to Swift's guard statement: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/statements/#Guard-Statement
Kiryl Dziamura said:
Oh, now I see the intention of the first two options:
path = paths.first() ?? return "" Ok(path) = paths.first() else return ""I don't like them because they unexpectedly mix two statements, but they don't increase the indentation.
I'd like to +1 this: it would be nice to avoid additional indentation (as long as it meshes well with the rest of the language)
If I'm following correctly, with the if Ok like Rust we would get code that goes from something like:
first = List.first(vals) ?? return 123
op = List.first(ops) ?? return first
... perform work with first and op ...
to
if Ok(first) = paths.first() {
if Ok(op) = ops.first {
... perform work with first and op ...
} else {
return first
}
} else {
return 123
}
or the equivalent with when/match:
when vals is
[] -> 123
[first, .. as restvals] ->
when ops is
[] -> first
[op, .. as restops] ->
... perform work with first and op ...
I'm certainly not pushing for early return after ?? if it doesn't mesh well with the language, just would like a construct that gives me the equivalent power to avoid indentation.
Though of course there's always this, which avoids additional indentation:
first = if List.len(vals) == 0 then
return 123
else
List.first(vals) ?? crash "expected non-empty list"
...
# continue using first
But it feels off having to check length first, then calling first() with returns a Result and having to either ? which forces the outer function to return a Result (not ideal), or like in this snippet, "forcing" a crash even though it shouldn't be possible.
Maybe some form of flow-analysis avoids the Result from List.first, but that sounds complicated.
one of my learnings from Ruby is not to have an unless
having two ways to do if bothers a lot of people
e.g. you get into situations like someone writes an unless and then later comes in and wants to add an else, but then it's like "should it be unless...else? Or now do we switch it around to if...else?"
Kiryl Dziamura said:
Kiryl Dziamura said:
Recently I read Vine docs, found a bit awkward yet interesting take on the related syntax (also the implication operator):
https://vine.dev/docs/features/conditions#the-is-operator
Speaking of roc,
Does this look weird?
if paths.first() is Ok(path) { File.delete!(path)? }Does this look even weirder?
if paths.first() is not Ok(_) { return 42 }
I’m still wondering what do you guys think about this :grinning_face_with_smiling_eyes:
It's not ideal, but it eliminates both the
=vs==problem andlet else
I don't think the lack of negative pattern matching is a problem, since Roc doesn't have one right now. Also, when you need that, seems like a guard pattern match on the negative case (like the one proposed here) solves the problem.
When I was learning Rust, I found if-let pretty unintuitive. Especially without capturing (the programmer should have used matches! in that case, but – sharing Brendan's view –, I hope in Roc, we plan to support one way of doing this). I understood the concept, but the whole syntax seemed to be in reverse. At least the let notified me that i'm dealing with a pattern match disguising itself as an assignment.
// Okay, i guess we are assigning to _, which is ignored
if Err(_) = my_result {
return 3
}
// This just seems wrong, despite being a completely valid use case,
// since there is no assignment here at all
if Red = color {
return 3
}
I like the is syntax. I'm not a newcommer to pattern matching, so I might just not see why is could be confusing, since I immediately think that inside the patter, there is a capture, not a variable that will be substituted and checked for equality.
The when a is ... expression used is as well, and I always found that elegant.
I also forgot to note that currently ?? return "..." can also be used as ?? crash "..." e.g. in short scripts, which is handy
the problem with is is that, for example, a valid Python expression is (a is b) just like how (a == b) is a valid Python expression
But you have taught people Rust Richard. Did you find beginners be confused with if let? If not, my concerns aren't as relevant and I'm left with a preference for the style.
I haven't taught if let to beginners
but personally, I don't like how if let reads in Rust, but I'm (for whatever reason) fine with if Ok(path) = paths.first() {
it might be because if let is just a strange thing to read on its own
like if takes a condition, and let isn't a condition, it's a statement
so it's strange to see if followed immediately by let
whereas in if Ok(path) = paths.first() it's if followed by a pattern, which is already a form of condition in a match
oh, another idea I just thought of:
if Ok(path) => paths.first() {
}
so reuse the same => from match
Yeah, makes sense you didn't teach that. Well, I get the concern with the python example. Maybe error messages would help there saying that a pattern was expected there.
my concern with is is less on the writing and more on the reading
Yes, if and let together was confusing as a beginner.
like you read it and you are confident (but incorrect) that you understand what it's doing, based on habit from other languages
That's true.
Richard Feldman said:
oh, another idea I just thought of:
if Ok(path) => paths.first() { }
I think the arrow doesn't fit here. Makes me think that Ok(path) stands on it's own and after evaluation, comes the paths.first() part, since that's the direction.
A breakdown of how I got to one of the already proposed ideas. Also some thoughts on related syntax (if you don’t care - just jump to the last two points):
res?
Just a good old question postfix operator. Use to early return whatever error with convenient chaining
res.map_err(|_| -> "")?
Use to early return mapped error with convenient chaining.
I'm not a fan of infix ?. I don't see how it helps especially considering chaining. Yes, it's short, but this is the only advantage imo
res.ok_or(def)
This doesn't allow early return but allows convenient chaining. Use to unwrap with default value
res ?? expr
The return statement lives inside of blocks which are expressions (and short circuit). Meaning it should have expr in the "else" branch. Use to either unwrap with default value, or to early return
x = res ?? 42
y = res ?? { return 42 }
But! :point_down:
Ok(x) = res ?? expr
Instead of locking syntax only on the result, it’s possible to extend it to other patterns. In the same fashion, expr is short circuit, early return is possible, and indentation doesn’t suffer. Chaining is not possible. Use to either unwrap with default value, or to early return
Ok(x) = res ?? 42
Ok(y) = res ?? { return 42 }
I do like how res ?? { return 42 } reads!
So for the generic unwrap, ?? would depend on the result of the pattern match, not only on the data, right? Meaning the operator works like this:
(Ok(x) = res) ?? 42
(Ok(y) = res) ?? { return 42 }
It took me a while to realize this (if that is indeed what's happening), since (once again I come back to this point) the assignment makes me think that's how the operator would work:
Ok(x) = (res ?? 42)
Ok(y) = (res ?? { return 42 })
I think it's fine with just the pattern match part, but with the ?? thrown into the mix, it's getting hard to understand. Specifically, I would rather expect this to work
Ok(x) = res ?? Ok(42)
If your plan is to generalize from Results, we would need a way to handle more than 1 args. But at that point, the verbosity starts to subsume the convenience of??.
Coord(x, y) = res ?? Coord(42, 24)
The point of something like res ? error_mapper is that I see a lot of blind error propagation in Rust which loses stack context, and I think we should make it as easy as possible to provide that context, which this solution does by only needing one character and two spaces, almost the minimum possible
I feel like this conversation has become so sprawling that it's hard to digest what is and is not begin proposed anymore - or what problem(s) we would like to solve.
It feels like we want to overhaul the entire syntax in the language for:
So maybe we should sit down and catalog all of the different things we are trying to solve for, where they can occur, and potential syntactic constructions to solve for reach. Hopefully that way we can winnow down to a consistent set of syntax that solves these problems in a way that seems consistent and well thought out.
And selfishly in a way where I know what to implement in the parser :rofl:
here is a concrete proposal that I'm happy with. If anyone would rather we didn't do this, please say why!
?? return "" formats to have the formatter add braces so it becomes ?? { return "" } and same with crash. No changes to the semantics of anything involved in this; it's purely to address the concern over it being not visually obvious enough what the code does.if Ok(path) = paths.first() { pattern matching. We explored a bunch of alternatives and this still feels like the best solution to the problem at the top of the thread.that's it, no other changes. Both of these are addressing specific ergonomics concerns with the status quo, and are not trying to go back to the drawing board and reconsider everything.
Sounds like we want to address these use cases:
Sam Mohr said:
The point of something like
res ? error_mapper
But what about chaining?
Anthony Bullard said:
It feels like we want to overhaul the entire syntax in the language...
That's probably my bad :sweat_smile: it just everything pulls everything and as a result, I see a combinatorial explosion in variants
Richard Feldman said:
here is a concrete proposal that I'm happy with. If anyone would rather we didn't do this, please say why!
- Change the way
?? return ""formats to have the formatter add braces so it becomes?? { return "" }and same withcrash. No changes to the semantics of anything involved in this; it's purely to address the concern over it being not visually obvious enough what the code does.- Introduce
if Ok(path) = paths.first() {pattern matching. We explored a bunch of alternatives and this still feels like the best solution to the problem at the top of the thread.that's it, no other changes. Both of these are addressing specific ergonomics concerns with the status quo, and are not trying to go back to the drawing board and reconsider everything.
This is the value of having a BDFN
When you ask "what's with chaining", just wanna clarify that binop ? can only be used at the end of a chain, as is the case with ??. Only suffix ? can be used in chains
Just for formatting, do we ever force newlines for a single statement brace block?
nah
Yeah, sounds good
The alternative would suck for tersity
Today all blocks (with braces) are multiline in the new formatter
So that will be a sort of custom/new change
The formatter actually doesn't use braces at all for single statements blocks that have no newlines or comments
It should be easy to change now that I get rid of Body/Block as a separate type and they are just exprs now
Anthony Bullard said:
Sounds like we want to address these use cases
I would also add aesthetics/ergonomics dimensions such as
Short circuit works really well in too cases: Boolean operations and in languages with a concept of null. In a language like Roc where Result is just a builtin datatype (but not primordial to the language) our only real choice is recovery or early return
Yeah, I mean short circuiting as conditional evaluation of expressions. If such expression is a block, it may end with a return or crash statement which makes early return possible. So early return makes sense only inside of "short circuits". That's how I see it.
semantically, I think it's simpler for return and crash to be expressions, but I think formatting them to have braces around them after a ?? looks better :big_smile:
I don't think we should introduce more complicated rules than "they're expressions" though
the downside of "if they're expressions, it means you can technically use them in a way that you'd never use them" seems inconsequential to me :stuck_out_tongue:
and everybody already knows all the rules around expressions, so it doesn't require introducing a new concept to the language
they just go in the expression bucket with everything else
- Change the way
?? return ""formats to have the formatter add braces so it becomes?? { return "" }and same withcrash. No changes to the semantics of anything involved in this; it's purely to address the concern over it being not visually obvious enough what the code does.
What's the visually confusing part of ?? return "foo"? Isn't it as confusing/clear as foo() ? Bar? (kmn if I accidentally just triggered adding braces around that as well :joy:)
?, ? , and ?? are some top-tier features and they existing specifically to make error handling ergonomic so that people will expend the energy to add tags/context (which they otherwise don't, as experience has shown us). Shouldn't we have a high bar for making these features less ergonomic?
Now that we have static dispatch do we expect to see issues with ? and ?? ?
They no longer play nice with pipelines
out =
(((load_config(in_file) ?? default_config)
.stage1(abc) ? Stage1Err)
.stage2() ? Stage2Err)
.stage3() ? Stage3Err
I think this is the root issue why we may want to nix or redesign them.
I think we should wait to see if it's a problem in practice
so far in all the cases I've seen in the realworld app, which uses dispatch everywhere, it wouldn't have mattered
and I think these operators have had a problem with scope creep, which makes me extra hesitant to redesign them for hypothetical problems that haven't come up in practice :big_smile:
Hello, I usually just read content here (it's like watching a good tv series to me :smile:) but this time I couldn't resist to add something to the story :upside_down: .
My proposal is to treat an if expression as a syntactic sugar for specific match expressions.
So an if expression in its full form:
if <cond > { patternIf => <exprIf> } else { patterElse => <exprElse> }
would be translated to:
match <cond> { patternIf => <exprIf> patternElse => <exprElse> }
but you can omit sam parts. If patternIf is omitted it is assumed to be True =>. If patternElse is omitted it is assumed to to be _ => . If whole else part is omitted it is assumed to be else { _ => {} }
Than you can write normal if expression:
if a < 5 { a } else { 5 }
but also use enhanced if expression when an equivalent match expression would be to verbose:
if paths.first() {Ok(path) => File.delete!(path)?}
if paths.first() {Ok(path) => File.read_utf8!(path)? } else { "" }
if my_list { [a] => calculate(a) } else { crash "expecting exactly one record" }
if some_condition {False => <look, this is important> } else { <never mind > }
From teaching perspective, you can introduce first a simple if expression then the match expression and than you can say: in case of such a simple, two-branch match expressions you can use this enhanced if expresion for short, and introduce the full form of if expression.
Welcome to the conversation @Jacek Rembisz :)
Kiryl Dziamura said:
Can
??be used only for the early return? The question marks suggests it in some sense. It might be implied as parametric unwrap where both the expression and the return value are not necessarilyResults.When you don't need early return - use
.with_defaultOk(path) = paths.first() ?? "" # return empty string if not Ok # desugars to match paths.first() { Ok(path) => # continuation _ => "" }
What about just special syntax to allow a single branch of a match to be a continuation?
match paths.first() {
Ok(path) => ..
_ => ""
}
# path is in scope here
...or for if statements:
# instead of
if (badCondition) { return Bad }
# other stuff
# what about
if (badCondition) { Bad } else ..
# other stuff
Last updated: Jun 16 2026 at 16:19 UTC