Conditionals
Let's start with the basics. You've got a condition, you need to do different things based on it. Classic programming stuff.
If Expressions
In Coco, if is an expression—it returns a value. This is cleaner than having separate statements and ternary operators.
// Basic if/else
if $temperature > 30 {
echo "It's hot";
} else {
echo "It's fine";
}
// As an expression (returns a value)
$message = if $temperature > 30 { "It's hot" } else { "It's fine" };
// Chaining conditions
$description = if $temperature > 30 {
"hot"
} else if $temperature > 20 {
"warm"
} else if $temperature > 10 {
"cool"
} else {
"cold"
};
When you use if as an expression, both branches must return the same type. The compiler won't let you return a string from one branch and an integer from another.
Truthiness
Coco doesn't do JavaScript-style truthiness where empty strings and zeros are falsy. Only bool values work in conditions.
$name = "";
// This won't compile
if $name { // Error: expected bool, got string
echo "Has name";
}
// Be explicit
if $name.len() > 0 {
echo "Has name";
}
// Or use is_empty()
if !$name.is_empty() {
echo "Has name";
}
Why? Because implicit conversions hide bugs. Is 0 false because it's zero, or is it a valid value? Is an empty array false, or is it a valid empty collection? Being explicit removes the ambiguity.
Boolean Operators
The usual suspects, with short-circuit evaluation:
// AND - second expression only evaluated if first is true
if $logged_in && $has_permission {
allow_access();
}
// OR - second expression only evaluated if first is false
$name = $user.name || "Anonymous";
// NOT
if !$is_admin {
restrict_access();
}
Short-circuit evaluation isn't just an optimization—it lets you write guard clauses:
// Safe: second condition only checked if $user isn't null
if $user != null && $user.is_active {
process($user);
}
Comparing Values
Standard comparison operators:
$a == $b // Equal
$a != $b // Not equal
$a < $b // Less than
$a <= $b // Less or equal
$a > $b // Greater than
$a >= $b // Greater or equal
For custom types, you can implement the Comparable trait to enable these operators. More on that in the OOP chapter.
When to Use If vs Match
If statements are great for simple boolean conditions. But when you're checking multiple cases or destructuring values, pattern matching is usually clearer:
// If chain - okay for simple cases
if $status == "pending" {
queue();
} else if $status == "ready" {
process();
} else if $status == "done" {
archive();
}
// Match - better when you have many cases
match $status {
"pending" => queue(),
"ready" => process(),
"done" => archive(),
default => log_unknown($status)
}
The match version is more readable, and the compiler ensures you handle all cases. We'll explore pattern matching in depth in the next section.
Guard Clauses
A common pattern is using early returns to handle edge cases first:
fn process_user($user ?User): !Result {
if $user == null {
return error("User is required");
}
if !$user.is_active {
return error("User is inactive");
}
// Main logic here, unindented
$data = fetch_data($user.id);
return Ok($data);
}
This keeps your happy path at the lowest indentation level, making the code easier to follow. It's sometimes called "early return" or "bouncer pattern."
Best Practices
Keep conditions simple. If you need complex boolean logic, extract it into a well-named variable or function:
// Hard to read
if $user != null && $user.is_active && ($user.role == "admin" || $user.has_permission("edit")) {
// ...
}
// Better
$can_edit = $user != null && $user.is_active &&
($user.role == "admin" || $user.has_permission("edit"));
if $can_edit {
// ...
}
// Even better
fn can_edit($user ?User): bool {
if $user == null || !$user.is_active {
return false;
}
return $user.role == "admin" || $user.has_permission("edit");
}
Prefer positive conditions. if $is_valid reads better than if !$is_invalid.
Use match for exhaustive cases. When you need to handle all variants of an enum, match forces you to be complete.