Replies: 5 comments 1 reply
-
My first reaction is that Just today I found its pattern matching super helpful: pub fn as_call(self : Message) -> Request? {
guard self is Request(req) else { return None }
guard req.id is Some(_) else { return None }
Some(req)
} This would have been much more verbose with multi-line "match" statements (which honestly I really prefer to avoid whenever possible). Additionally, I'm having troubles understanding "Example 2" above: when x > 0 => Err("x must be positive");
when y != 0 => Err("y must not be zero");
when x < 100 && y < 100 => Err("values too large"); So when "x > 0", return an error that "x must be positive"... is this a typo? If those are simply typos, then I can see the value of a quick and easy return... however, I really like the pattern-matching capabilities of the guard statement and would sorely miss it if it were removed. Maybe there is room for both |
Beta Was this translation helpful? Give feedback.
-
The original intent of the From what I’ve seen in other languages, guard statements are meant to conditionally allow execution to continue based on a boolean expression. They’re commonly used as a clear and concise way to exit early when a condition isn't met—similar in spirit to early return or error propagation patterns in other languages. At their core, guard statements are designed to simplify early-exit logic and improve code clarity in a few key ways: Early Exit / Fail FastIf the condition evaluates to Reduced Cognitive LoadInstead of deeply nested Error HandlingThey're widely used for validating input, checking invariants, and enforcing preconditions—core tasks in writing robust code. Because guard expresses a common early-exit pattern in a standardized form, it gives the compiler more room to analyze control flow and apply optimizations—such as better jump prediction, streamlined branching, or lower-level code generation.
These kinds of optimizations are harder to apply when equivalent logic is expressed using scattered On the pattern matching side—yes, you’re right to bring up the That said, we shouldn't conflate flexibility with good design. If we want to generate better optimized code and reduce complexity, we should lean toward constructs that limit flexibility in favor of clarity and structure. For everything else, we still have I was trying to come up with a syntax that complements pattern matching both syntactically and logically, but focuses specifically on filtering out error cases to clear the way for the happy path. The goal is to encourage a flat, linear style—avoiding nesting and improving readability, especially when there are multiple early-exit conditions. Pattern matching is ideal for structural validation—checking if a value has the right shape—but it becomes verbose or awkward when additional logical constraints are layered on. The
Yes — after several iterations, some mistakes slipped in. I was using AI to help come up with real-world use cases, but it kept falling back to outdated syntax or invalid logic instead of producing realistic examples. Thanks for taking the time to read and share your thoughts—it's helpful to get other perspectives. |
Beta Was this translation helpful? Give feedback.
-
If I understand correctly, I think what you proposed is just: if something is failure {
let result = f(failure)
return result
}
return success While guard is the exact opposite: guard something is success else {
return failure
}
let result = f(success)
return result |
Beta Was this translation helpful? Give feedback.
-
Hi all! My two cents on this:
Using if result is Err(error) {
match err { ... } // or even `return err`
} Which is already addressed by Small Counter-Proposal That said, what I was thinking is that fn ok(toggle: Bool) -> String {
guard toggle else {
// The implicit value is useless so is discarded by compiler
return "a"
}
return "ok"
} Let's evolute it a bit: fn message_from_future(year: Uint16) -> String {
guard num < 2026 else {
// Here the implicit value is involved and compiler already knows what to match over
2026..<3026 => "You're in the next millennium!"
_ => "A future too far away, dude!"
}
year
} An other more interesting one: fn check(year~: Result[UInt64, @strconv.StrConvError] = @strconv.parse_uint64?("2025")) -> UInt64! {
guard year is Ok(num) else {
@strconv.StrConvError(message) => fail!(message)
}
num
} Instead of adding more syntax and keyword, or even break the guard backward compatibility, why not simply enhance it to be able to manage each possible case? |
Beta Was this translation helpful? Give feedback.
-
@Metalymph I believe there are a few misunderstandings. We are trying to come up with a convenience; otherwise, A The
Let's have a simple example: fn greet(person: [String: String]) {
guard let name = person["name"] else {
print("No name provided.")
return
}
print("Hello, \(name)!")
} While the 'guard' terminology can make sense metaphorically by conveying the idea of guarding assumptions, there are a few downsides associated with the word From your third argument, I believe we completely agree on the use of the Regarding your suggestion to enhance guard: while it is an interesting idea, the core inverted flow of The same logic with a fn greet(person: [String: String]) {
when let name = person["name"] => Err("Missing name");
// Use name here
} The code reads: when the condition is satisfied, do the binding; else, exit immediately. The arrow syntax provides clarity and forces us to return a single value by collapsing multiple failing conditions into a single value, maintaining a flat, linear code structure. While pattern matching branches into multiple distinct execution paths based on data structure or condition, The word when may not be the best, but it is the clearest option I could come up with to convey our intent. I am open to suggestions, and in my original proposal, I also suggested alternative keywords to better match the intended meaning. If there are multiple points of failure that would otherwise require separate guard clauses, we can stack them up, as I showed in earlier examples. when x > 0 => Err("x must be positive");
when y != 0 => Err("y must not be zero");
when x < 100 && y < 100 => Err("values too large");
// use x and y here Now, the code reads naturally, with absolute clarity. The The While backward compatibility is important, I believe it should not constrain us at this point. Our focus should be on designing the best possible syntax, learning from the experiences of other programming languages, even if it requires careful adjustments later. I am not saying we should break the existing syntax for trivial reasons, but the way I see it, we are trying to create the best possible syntax. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Overview
Replace the current
guard ... else { ... }
syntax with a cleaner, more intuitive expression form:This expression enables concise, readable, and fail-fast logic for early returns in functions—particularly useful with
Result
,Option
, orfail!
.Motivation
The
guard ... else { ... }
statement in MoonBit provides a mechanism for early exit, but it often feels cryptic and awkward to use in practice. Developers are required to reason in reverse — writing what must be true to continue, rather than directly expressing what causes a failure. This inversion is unnatural and can lead to misunderstandings, especially for new users or those coming from other languages.Furthermore, the syntax is visually noisy:
else {}
block adds boilerplate even for simple returnsFor example, in a function checking input validity:
This reads as: “Assume this is not empty; otherwise return an error.” But in real code, the intent is usually to detect failure conditions directly: “If this is empty, return an error.”
This mismatch makes guard feel more difficult to reason about and less natural for expressing failure cases.
By replacing
guard
withwhen
, we allow developers to write fail-fast logic in a way that mirrors natural reasoning: describe the failure condition directly, then exit cleanly.Proposed Syntax
Semantics
This construct fully replaces
guard ... else { ... }
, offering a clearer and less verbose experience.Examples
Example 1: Parsing
Example 2: Input Validation
Example 3: Index Check (Option)
Example 4: Required Field (Replicating docs)
Example 5: Fail Fast
Example 5: Multiple
when
in a PipelineComparison with Existing Syntax
guard
)when =>
)guard s != "" else { Err(...) }
when s == "" => Err(...)
guard i < arr.length else { None }
when i >= arr.length => None
guard valid else { fail!(...) }
when !valid => fail!(...)
Reasoning Comparison:
guard
vswhen =>
guard
when =>
else { ... }
blockWhy
=>
Instead of{}
The choice to use
=>
instead of a block-basedelse { ... }
syntax is deliberate. The=>
token:match
, Scala'scase =>
, JavaScript arrow functions)Encourages Thinking in Terms of Failures
Another subtle but powerful benefit of using
=>
is that it naturally nudges developers to think in terms of failure cases. Rather than expressing what must be true (as inguard
) and then writing an inverted fallback, developers instead express:This maps closely to how humans reason about validation, checks, and failures in real-world logic. The
=>
token visually and semantically reflects a directional control flow: from a known error condition toward an early return.Using
=>
also allows:when
expressionswhen
and traditional control blocksThe simplicity and readability of
when <cond> => <value>
mirrors the mental model of: “If this condition is true, immediately yield this result.”Why
when
?The keyword
when
was chosen because it reads naturally in the context of early-exit logic:Alternatives Considered
We explored a number of one-word and minimal alternatives to replace guard. Our goals were:
fail
Option
exit
check
if
do
Grammar Considerations
when
is a keyword expression that evaluates the condition.=>
separates the fallback/early-return value.<fallback_value>
must match function return type.<condition>
must evaluate toBoolean
.Conclusion
Replacing
guard ... else { ... }
withwhen =>
results in clearer, more expressive fail-fast logic that better aligns with human reasoning. Thewhen =>
syntax makes error conditions visually and semantically clear, improves ergonomics, and reduces boilerplate—all while keeping the language syntax minimal and familiar.This proposal aims to simplify error-handling syntax in MoonBit by providing a more direct alternative to guard. I believe when => improves clarity in everyday code, reduces boilerplate, and fits naturally with pattern-matching-based control flow. It aligns well with the language’s existing principles and helps reduce the chance of errors in early-exit scenarios.
I'd appreciate feedback, ideas, or counterpoints from others who have worked with guard in real code. It’s intended as a practical refinement to patterns already common in real-world MoonBit code and worth considering on those merits.
Beta Was this translation helpful? Give feedback.
All reactions