Skip to content

Operators

Operators are special symbols that tell Ferret to perform specific operations on values. Think of them as the verbs of programming - they let you add, compare, combine, and transform data.

In this guide, you’ll learn all the operators Ferret provides and how to use them.

These operators perform math operations on numbers - just like a calculator!

OperatorNameExampleResultWhat it does
+Addition5 + 38Adds two numbers
-Subtraction5 - 32Subtracts second from first
*Multiplication5 * 315Multiplies two numbers
/Division15 / 35Divides first by second
%Modulo17 % 52Gets remainder after division
**Exponentiation2 ** 38Raises first to power of second

Let’s see them in action:

let sum := 10 + 5; // 15
let difference := 10 - 5; // 5
let product := 10 * 5; // 50
let quotient := 10 / 5; // 2
let remainder := 10 % 3; // 1 (10 divided by 3 is 3 remainder 1)
let power := 2 ** 4; // 16 (2 × 2 × 2 × 2)

The modulo operator % is particularly useful when you need to know if a number is even (remainder 0 when divided by 2) or for wrapping values around (like going from hour 23 to hour 0).

let is_even := 10 % 2 == 0; // true
let is_odd := 11 % 2 == 1; // true

Important: Ferret allows arithmetic between compatible numeric types. Smaller types are implicitly widened to a larger compatible type, and the result uses that widened type. Narrowing or lossy conversions still require an explicit cast.

let a: i16 = 100;
let b: i32 = 200;
// ✅ OK: a is widened to i32
let result := a + b; // i32

If a conversion could lose precision, use as:

let a: i32 = 100;
let b: f32 = 3.5;
// ❌ ERROR: incompatible numeric types (potential precision loss)
// let result := a + b;
// ✅ CORRECT: choose a wider float
let result := (a as f64) + (b as f64); // f64

This applies to all arithmetic operators: +, -, *, /, %, and **.

Ferret’s numeric rules keep math safe and predictable:

  • No hidden lossy conversions - only safe widening is implicit
  • Performance - the compiler knows the exact result type
  • Explicit intent - casts show when precision might be lost

Integers of different sizes:

let small: i32 = 10;
let large: i64 = 1000000000;
// Implicit widening to i64
let sum := small + large; // i64
// Explicit narrowing if you really want it
let sum32 := small + (large as i32); // i32

Integers and floats:

let tiny: i8 = 42;
let floating: f32 = 3.14;
// Lossless widening is allowed
let result := tiny + floating; // f32
let integer: i32 = 42;
// Potentially lossy: must cast explicitly
let result2 := (integer as f64) + (floating as f64); // f64

Different float sizes:

let f32val: f32 = 3.14;
let f64val: f64 = 2.718;
// Implicit widening to f64
let result := f32val + f64val; // f64

Comparison operators compare two values and return either true or false. You use these constantly when making decisions in your code.

OperatorNameExampleResultWhat it checks
==Equal to5 == 5trueAre they the same?
!=Not equal to5 != 3trueAre they different?
<Less than3 < 5trueIs first smaller?
>Greater than5 > 3trueIs first bigger?
<=Less than or equal5 <= 5trueIs first smaller or same?
>=Greater than or equal5 >= 3trueIs first bigger or same?

Here are some practical examples:

let age := 18;
let is_adult := age >= 18; // true
let is_teenager := age >= 13 && age < 20; // true
let can_vote := age >= 18; // true
let needs_guardian := age < 18; // false
let score := 85;
let passed := score >= 60; // true
let perfect := score == 100; // false
let needs_improvement := score < 70; // false

Remember: Use == to check if things are equal, not =. The single = is for assignment (putting a value into a variable).

Logical operators combine or modify boolean values (true and false). They’re essential for building complex conditions.

OperatorNameExampleResultWhat it does
&&Logical ANDtrue and falsefalseBoth must be true
||Logical ORtrue or falsetrueAt least one must be true
!Logical NOTnot truefalseFlips true to false and vice versa

The && operator returns true only when both sides are true:

let has_ticket := true;
let has_id := true;
let can_enter := has_ticket && has_id; // true (both are true)
let has_money := false;
let has_time := true;
let can_go_shopping := has_money && has_time; // false (one is false)

The || operator returns true when at least one side is true:

let is_weekend := true;
let is_holiday := false;
let can_relax := is_weekend || is_holiday; // true (one is true)
let is_raining := false;
let is_snowing := false;
let need_umbrella := is_raining || is_snowing; // false (both are false)

The ! operator flips the boolean value:

let is_logged_in := true;
let is_guest := not is_logged_in; // false
let has_errors := false;
let is_valid := not has_errors; // true

You can combine multiple logical operators to create complex conditions:

let age := 25;
let has_license := true;
let has_insurance := true;
let can_drive := age >= 16 && has_license && has_insurance;
let is_student := false;
let is_senior := false;
let gets_discount := is_student || is_senior;

Ferret evaluates logical expressions from left to right and stops as soon as it knows the answer:

let x := 0;
// Safe: if x is 0, the second part never runs
let safe := x != 0 && 10 / x > 1;
// This would crash if x is 0!
// let unsafe := 10 / x > 1 && x != 0;

This is called “short-circuit evaluation” - if the first part of && is false, Ferret doesn’t bother checking the second part because the result will be false anyway.

Assignment operators put values into variables. They can also modify the existing value at the same time.

OperatorNameExampleEquivalent toWhat it does
=Assignmentx = 5-Put 5 into x
+=Add and assignx += 3x = x + 3Add 3 to x, store result in x
-=Subtract and assignx -= 3x = x - 3Subtract 3 from x, store in x
*=Multiply and assignx *= 3x = x * 3Multiply x by 3, store in x
/=Divide and assignx /= 3x = x / 3Divide x by 3, store in x
%=Modulo and assignx %= 3x = x % 3Get remainder of x/3, store in x
**=Power and assignx **= 3x = x ** 3Raise x to power 3, store in x
^=XOR and assignx ^= 3x = x ^ 3Bitwise XOR x with 3, store in x

The compound operators (+=, -=, etc.) are shortcuts. They modify a variable based on its current value:

let score := 10;
score += 5; // score is now 15 (same as: score = score + 5)
score -= 3; // score is now 12 (same as: score = score - 3)
score *= 2; // score is now 24 (same as: score = score * 2)
score /= 4; // score is now 6 (same as: score = score / 4)
score %= 5; // score is now 1 (same as: score = score % 5)
score **= 2; // score is now 1 (same as: score = score ** 2)
score ^= 3; // score is now 2 (same as: score = score ^ 3)

These compound operators make your code shorter and often easier to read:

let lives := 3;
lives -= 1; // Lose a life - cleaner than: lives = lives - 1
let points := 100;
points *= 2; // Double points - cleaner than: points = points * 2

You’ve seen this one already! The walrus operator declares a variable or constant and lets Ferret automatically figure out its type.

let count := 42; // Type inferred as i32
let name := "Ferret"; // Type inferred as str
let active := true; // Type inferred as bool
let price := 19.99; // Type inferred as f64

It’s called the “walrus” operator because := looks like a walrus if you tilt your head! 🦭

The coalescing operator gives you a way to provide a default value when dealing with optional types. If the left side is none, it uses the right side instead.

let maybe_value: ?i32 = none;
let value := maybe_value ?? 0; // value is 0 (because maybe_value is none)
let some_value: ?i32 = 42;
let result := some_value ?? 0; // result is 42 (because some_value has a value)

It’s super useful for providing defaults:

let username: ?str = get_input();
let display_name := username ?? "Guest"; // Show "Guest" if no username
let max_items: ?i32 = get_config("max");
let limit := max_items ?? 100; // Default to 100 if not configured

This is especially useful with maps, since indexing returns optional values:

let scores := { "Alice" => 95, "Bob" => 87 } as map[str]i32;
let alice_score := scores["Alice"] ?? 0; // 95
let charlie_score := scores["Charlie"] ?? 0; // 0 (key not found)

Learn more about Optional Types and Maps.

Range operators create sequences of numbers (arrays). Ferret provides two variants:

  • .. - Exclusive end: doesn’t include the end value
  • ..= - Inclusive end: includes the end value
let exclusive := 0..5; // [0, 1, 2, 3, 4] - stops before 5
let inclusive := 0..=5; // [0, 1, 2, 3, 4, 5] - includes 5

This distinction is important when iterating:

run
import "std/io";
fn main() {
// Exclusive: iterates 0 to 4 (5 iterations)
for i in 0..5 {
io::Println(i); // Prints: 0, 1, 2, 3, 4
}
// Inclusive: iterates 0 to 5 (6 iterations)
for i in 0..=5 {
io::Println(i); // Prints: 0, 1, 2, 3, 4, 5
}
}

You can also specify a step/increment value with both operators:

let evens := 0..10:2; // [0, 2, 4, 6, 8] - exclusive, step by 2
let evens_inc := 0..=10:2; // [0, 2, 4, 6, 8, 10] - inclusive, step by 2
let odds := 1..10:2; // [1, 3, 5, 7, 9] - exclusive
let odds_inc := 1..=10:2; // [1, 3, 5, 7, 9] - inclusive (10 not divisible by step)

Steps default to 1 when omitted: start..end and start..=end. Float steps require float endpoints:

let thirds := 0.0..1.0:0.25; // [0.0, 0.25, 0.5, 0.75]
// 0..8:1.5 is invalid; use 0.0..8.0:1.5 instead

When to use which?

  • Use .. when you want to exclude the end (like array indices: 0..len(&arr))
  • Use ..= when you want to include the end (like counting days: 1..=7 for a week)

The dot operator lets you access fields (data) and methods (functions) that belong to a value.

type Point struct {
.X: i32,
.Y: i32,
};
let p: Point = { .X = 10, .Y = 20 };
let x_value := p.X; // Access the X field: 10
let y_value := p.Y; // Access the Y field: 20

You’ll use the dot operator constantly when working with strings, arrays, and custom types:

let message := "Hello";
let length := len(&message); // Get the length of the string
let numbers := [1, 2, 3, 4, 5];
let first := numbers[0]; // Get the first element

Bitwise operators work with the individual bits (0s and 1s) that make up numbers. These are advanced operators you’ll use when doing low-level programming, working with flags, or optimizing performance.

OperatorNameExampleResultWhat it does
&Bitwise AND5 & 31Bits on in both
|Bitwise OR5 | 37Bits on in either
^Bitwise XOR5 ^ 36Bits on in one, not both
~Bitwise NOT~5-6Flips all bits

Bitwise operators work only on integer types (including byte), and both operands must have the same type.

Here’s a quick example using binary literals (numbers starting with 0b):

let a := 0b1010; // 10 in binary
let b := 0b1100; // 12 in binary
let and_result := a & b; // 0b1000 (8)
let or_result := a | b; // 0b1110 (14)
let xor_result := a ^ b; // 0b0110 (6)
let not_result := ~a; // flips all bits

Don’t worry if this seems complex - most of the time you won’t need bitwise operators. They’re mainly used for:

  • Working with hardware or network protocols
  • Storing multiple flags efficiently
  • Performance-critical code

When you have multiple operators in one expression, Ferret follows specific rules about which one to do first. This is called “operator precedence” or “order of operations” - just like in math class!

Here’s the order from highest priority (done first) to lowest (done last):

  1. Member access - ., ?.
  2. Unary operators - !, -, +, ~
  3. Exponentiation - **
  4. Multiplication, Division, Modulo - *, /, %
  5. Addition, Subtraction - +, -
  6. Comparison - <, >, <=, >=
  7. Equality - ==, !=
  8. Bitwise AND - &
  9. Bitwise XOR - ^
  10. Bitwise OR - |
  11. Logical AND - &&
  12. Logical OR - ||
  13. Coalescing operator - ??
  14. Assignment - =, +=, -=, etc.

Let’s see this in action:

let result := 2 + 3 * 4; // 14 (multiply first: 3*4=12, then: 2+12=14)
let explicit := (2 + 3) * 4; // 20 (parentheses first: 2+3=5, then: 5*4=20)
let complex := 10 + 5 * 2 - 3; // 17 (5*2=10, 10+10=20, 20-3=17)
let clearer := 10 + (5 * 2) - 3; // Same, but easier to read

When in doubt, use parentheses ( ) to make your intention clear. It doesn’t hurt and makes your code easier to understand:

// Harder to read
let score := points * multiplier + bonus - penalty;
// Easier to read - same result!
let score := (points * multiplier) + bonus - penalty;

Your future self (and your teammates) will thank you!

You’ve learned about all the operators in Ferret! Here’s what we covered:

  • Arithmetic operators for math: +, -, *, /, %, **
  • Comparison operators for checking relationships: ==, !=, <, >, <=, >=
  • Logical operators for combining conditions: &&, ||, !
  • Assignment operators for modifying variables: =, +=, -=, etc.
  • Special operators like := for type inference and ?? for defaults
  • Bitwise operators for low-level bit manipulation
  • Operator precedence and when to use parentheses

Now that you know how to work with values using operators, you’re ready to control the flow of your programs: