Optional Types
Optional Types
Section titled “Optional Types”Optional types are one of Ferret’s key safety features, helping you avoid null pointer exceptions.
What are Optional Types?
Section titled “What are Optional Types?”An optional type T? can hold either a value of type T or none. The none keyword is a constant (like true and false) that represents the absence of a value. This makes the possibility of missing values explicit in your type system.
let someNumber: i32? = 42; // Has a value (42)let noNumber: i32? = none; // No value (none is a constant like true/false)Important: Ferret doesn’t use wrapper types like Some() or None(). An optional T? is simply either a value of type T or the constant none.
Type Narrowing
Section titled “Type Narrowing”Ferret uses flow-sensitive typing to automatically narrow optional types in conditionals:
let x: i32? = 10;
if x != none { // Inside this block, x is narrowed to i32 (not i32?) let doubled: i32 = x * 2; // OK} else { // Inside this block, x is narrowed to none let value: i32? = x; // OK - can assign none to optional let num: i32 = x; // ERROR - cannot assign none to i32}With Equality Checks
Section titled “With Equality Checks”let opt: str? = "hello";
if opt == none { // opt is none here io::Println("No value");} else { // opt is str here (not str?) let length: i32 = opt.length;}Coalescing Operator
Section titled “Coalescing Operator”The coalescing operator ?? provides a default value when an optional is none:
let maybeValue: i32? = none;let value: i32 = maybeValue ?? 0; // value is 0
let someValue: i32? = 42;let result: i32 = someValue ?? 0; // result is 42Chaining Coalescing Operators
Section titled “Chaining Coalescing Operators”let a: i32? = none;let b: i32? = none;let c: i32? = 42;
let result: i32 = a ?? b ?? c ?? 0; // result is 42Assignment Rules
Section titled “Assignment Rules”Wrapping (T → T?)
Section titled “Wrapping (T → T?)”You can assign a value to an optional type (automatic wrapping):
let num: i32 = 42;let optNum: i32? = num; // OK - wrapped automaticallyUnwrapping (T? → T)
Section titled “Unwrapping (T? → T)”You cannot directly assign an optional to a non-optional:
let optNum: i32? = 42;let num: i32 = optNum; // ERROR - must unwrap firstInstead, use type narrowing or the coalescing operator:
// Option 1: Type narrowingif optNum != none { let num: i32 = optNum; // OK}
// Option 2: Coalescing operatorlet num: i32 = optNum ?? 0; // OKNone Assignment
Section titled “None Assignment”none can only be assigned to optional types:
let opt: i32? = none; // OKlet num: i32 = none; // ERRORFunctions with Optional Return Types
Section titled “Functions with Optional Return Types”fn findUser(id: i32) -> User? { if userExists(id) { return getUser(id); } return none;}
// Using the resultlet maybeUser: User? = findUser(42);
if maybeUser != none { io::Println(maybeUser.name);} else { io::Println("User not found");}Best Practices
Section titled “Best Practices”- Use optional types for values that might be absent
- Use type narrowing to safely access optional values
- Use coalescing operator for simple default values
- Don’t use optional types unnecessarily
- Don’t try to use optional values without checking for none first
- Don’t chain too many coalescing operators (readability)
Examples
Section titled “Examples”Safe Division
Section titled “Safe Division”fn safeDivide(a: i32, b: i32) -> i32? { if b == 0 { return none; } return a / b;}
let result: i32? = safeDivide(10, 2);let value: i32 = result ?? 0; // value is 5Working with Maps
Section titled “Working with Maps”Optionals and maps work together beautifully since map access always returns optional values:
let scores := { "alice" => 95, "bob" => 87} as map[str]i32;
// Map access returns i32?let alice_score: i32? = scores["alice"]; // Returns i32? with value 95let carol_score: i32? = scores["carol"]; // Returns i32? with value none
// Use coalescing for defaultslet score1 := scores["alice"] ?? 0; // 95let score2 := scores["carol"] ?? 0; // 0
// Chain lookups with multiple fallbackslet primary := scores["primary"] ?? scores["backup"] ?? 0;See the Maps section for more details on how maps use optionals for safety.
Configuration with Defaults
Section titled “Configuration with Defaults”type Config struct { .port: i32?, .host: str?};
let config := { .port: none, .host: "localhost"} as Config;
let actualPort: i32 = config.port ?? 8080;let actualHost: str = config.host ?? "0.0.0.0";Next Steps
Section titled “Next Steps”- Learn about Maps - See optionals in action with collections
- Explore error handling - Handle errors safely
- Understand Structs - Create custom types