Null Safety & Optionals
Lidar com valores null/nil é uma fonte comum de bugs. Veja como diferentes linguagens abordam a segurança contra null.
Tipos Nullable & Optionals
- JavaScript
- PHP
- Rust
- Go
- Python
- Zig
- C#
- C++
- C
- Java
- Ruby
- Swift
// null and undefined are distinct
let a = null; // Intentionally empty
let b = undefined; // Not yet assigned
let c; // undefined
// Checking for null/undefined
if (value === null) { }
if (value === undefined) { }
if (value == null) { } // checks both null & undefined
// Nullish coalescing (??)
const name = user ?? "default";
const age = data.age ?? 0;
// Optional chaining (?.)
const city = user?.address?.city;
const first = arr?.[0];
const result = func?.();
// Nullish coalescing assignment
let x = null;
x ??= "default"; // x is now "default"
// Logical OR (includes falsy values)
const name2 = user || "default"; // "" -> "default"
const name3 = user ?? "default"; // "" -> ""
<?php
// Null
$value = null;
is_null($value); // true
isset($value); // false
// Null coalescing
$name = $user ?? "default";
$name ??= "default"; // PHP 7.4+
// Null safe operator (PHP 8.0+)
$city = $user?->getAddress()?->getCity();
// Nullable types
function find(?int $id): ?User {
if ($id === null) return null;
return User::find($id);
}
// Union types with null
function process(string|null $value): string {
return $value ?? "default";
}
// Null strict comparison
$value === null; // Strict check
$value == null; // Also matches false, 0, ""
// No null! Use Option<T>
let some_value: Option<i32> = Some(42);
let no_value: Option<i32> = None;
// Pattern matching
match some_value {
Some(v) => println!("Got: {}", v),
None => println!("Nothing"),
}
// if let
if let Some(v) = some_value {
println!("Got: {}", v);
}
// Unwrap methods
some_value.unwrap(); // Panics if None
some_value.unwrap_or(0); // Default value
some_value.unwrap_or_default(); // Type's default
some_value.expect("must exist"); // Panic with message
// Combinators
some_value.map(|v| v * 2); // Some(84)
no_value.map(|v| v * 2); // None
some_value.and_then(|v| Some(v + 1)); // Some(43)
some_value.filter(|v| *v > 50); // None
no_value.or(Some(0)); // Some(0)
// ? operator (propagate None)
fn get_city(user: &Option<User>) -> Option<String> {
let city = user.as_ref()?.address.as_ref()?.city.clone();
Some(city)
}
// nil is the zero value for pointers, interfaces,
// maps, slices, channels, and functions
var ptr *int = nil
var slice []int = nil
var m map[string]int = nil
// Check for nil
if ptr != nil {
fmt.Println(*ptr)
}
// Pointer for optional values
func findUser(id int) *User {
// return nil if not found
return nil
}
user := findUser(1)
if user != nil {
fmt.Println(user.Name)
}
// Error pattern (idiomatic Go)
func getCity(user *User) (string, error) {
if user == nil {
return "", errors.New("user is nil")
}
if user.Address == nil {
return "", errors.New("address is nil")
}
return user.Address.City, nil
}
// Zero values (no nil needed)
var s string // "" (not nil)
var n int // 0 (not nil)
var b bool // false (not nil)
# None is Python's null
value = None
# Check for None (use 'is', not '==')
if value is None:
print("no value")
if value is not None:
print(value)
# Optional type hints
from typing import Optional
def find_user(id: int) -> Optional[User]:
# Returns User or None
return None
# Union syntax (Python 3.10+)
def find_user(id: int) -> User | None:
return None
# Default values
name = user.name if user else "default"
# Walrus operator for safe access
if (user := find_user(1)) is not None:
print(user.name)
# getattr with default
name = getattr(user, 'name', 'default')
# dict.get with default
city = data.get('city', 'unknown')
// Optional type: ?T
var value: ?i32 = 42;
var empty: ?i32 = null;
// Unwrap with orelse
const result = value orelse 0; // 42
const result2 = empty orelse 0; // 0
// Unwrap with if
if (value) |v| {
std.debug.print("Got: {d}\n", .{v});
} else {
std.debug.print("null\n", .{});
}
// Unwrap with .? (assert non-null)
const v = value.?; // panics if null
// Optional pointers
var ptr: ?*i32 = null;
// Chaining optionals
fn getCity(user: ?*User) ?[]const u8 {
const u = user orelse return null;
const addr = u.address orelse return null;
return addr.city;
}
// Nullable value types
int? nullableInt = null;
int? withValue = 42;
// Null check
if (nullableInt.HasValue)
Console.WriteLine(nullableInt.Value);
// Null-coalescing
string name = user ?? "default";
int age = nullableAge ?? 0;
// Null-coalescing assignment
string? name = null;
name ??= "default";
// Null-conditional (safe navigation)
string? city = user?.Address?.City;
int? length = text?.Length;
int? first = list?.FirstOrDefault();
// Nullable reference types (C# 8+)
#nullable enable
string nonNull = "hello"; // Cannot be null
string? canBeNull = null; // Can be null
// Pattern matching with null
if (value is not null) { }
if (value is string s) { }
// Null-forgiving operator
string s = possiblyNull!; // Trust me, not null
// Pointers can be null
int* ptr = nullptr;
if (ptr != nullptr) {
std::cout << *ptr;
}
// std::optional (C++17)
#include <optional>
std::optional<int> findValue(int id) {
if (id > 0) return 42;
return std::nullopt;
}
auto result = findValue(1);
// Check and access
if (result.has_value()) {
std::cout << result.value();
}
// value_or (default)
int val = result.value_or(0);
// Dereference (undefined if empty)
if (result) {
std::cout << *result;
}
// std::unique_ptr / std::shared_ptr
auto ptr = std::make_unique<int>(42);
if (ptr) {
std::cout << *ptr;
}
// NULL pointer
int* ptr = NULL;
// Check before use
if (ptr != NULL) {
printf("%d\n", *ptr);
}
// Common patterns
// Return NULL for "not found"
char* find_name(int id) {
if (id < 0) return NULL;
return "John";
}
char* name = find_name(-1);
if (name != NULL) {
printf("%s\n", name);
}
// Sentinel values (no optionals)
#define NOT_FOUND -1
int find_index(int* arr, int size, int target) {
for (int i = 0; i < size; i++) {
if (arr[i] == target) return i;
}
return NOT_FOUND;
}
// null reference
String name = null;
// NullPointerException if not checked
// name.length(); // throws NPE!
// Optional<T> (Java 8+)
Optional<String> opt = Optional.of("hello");
Optional<String> empty = Optional.empty();
Optional<String> nullable = Optional.ofNullable(name);
// Access
opt.get(); // "hello" (throws if empty)
opt.orElse("default"); // "hello"
empty.orElse("default"); // "default"
opt.orElseGet(() -> compute()); // Lazy default
// Transform
opt.map(String::toUpperCase); // Optional("HELLO")
opt.flatMap(this::findUser); // Flatten nested Optional
opt.filter(s -> s.length() > 3); // Optional("hello")
// Check
opt.isPresent(); // true
opt.isEmpty(); // false (Java 11+)
opt.ifPresent(s -> System.out.println(s));
// Chaining
Optional<String> city = Optional.ofNullable(user)
.map(User::getAddress)
.map(Address::getCity);
# nil is Ruby's null
value = nil
# Check for nil
value.nil? # true
value == nil # true
# Safe navigation operator (&.)
city = user&.address&.city
# Default with ||
name = user_name || "default"
# Default with ||= (assign if nil/false)
@name ||= "default"
# Dig method (safe nested access)
city = data.dig(:user, :address, :city)
# &. with method calls
length = text&.length # nil if text is nil
# Presence check (Rails)
# name.present? # false for nil, "", " "
# name.presence || "default"
# Compact (remove nils from array)
[1, nil, 2, nil, 3].compact # [1, 2, 3]
// Optionals (core language feature)
var name: String? = nil // Optional String
var age: Int? = 42 // Optional Int
// Forced unwrap (crashes if nil!)
let n = name! // Don't do this unless sure
// Optional binding (if let)
if let name = name {
print(name)
}
// Guard let (early return)
guard let name = name else {
return
}
print(name) // name is now non-optional
// Nil-coalescing
let result = name ?? "default"
// Optional chaining
let city = user?.address?.city
// Map / flatMap
let upper = name.map { $0.uppercased() }
let user = id.flatMap { findUser($0) }
// Implicitly unwrapped optional
var label: String! // Assumed non-nil after init
// Multiple optional binding
if let name = name, let age = age {
print("\(name) is \(age)")
}
Principais Conclusões
-
Tipos Option/Optional -- Rust (
Option<T>), Swift (T?), Zig (?T) e Java (Optional<T>) fornecem tipos opcionais explícitos em vez de referências anuláveis. Em Rust,let x: Option<i32> = Some(42)ouNone-- não existenull; você usamatch,if letouunwrap_or(0). Ovar name: String? = nildo Swift e ovar value: ?i32 = nulldo Zig tornam a opcionalidade parte do tipo. OOptional<String> opt = Optional.ofNullable(name)do Java encapsula valores anuláveis. Esses tipos forçam você a tratar a ausência explicitamente. Escolha Option/Optional quando quiser verificação em tempo de compilação; use referências anuláveis (JavaScript, Python, C) quando preferir flexibilidade e verificações em tempo de execução. -
Operadores de coalescência nula -- A maioria das linguagens suporta
??para valores padrão:const name = user ?? "default"em JavaScript,$name = $user ?? "default"em PHP,string name = user ?? "default"em C#,let result = name ?? "default"em Swift econst result = value orelse 0em Zig. Esses ignoram valores falsy como""ou0(diferente de||em JavaScript). Go não tem??; você usaif user != nil { name = user.Name } else { name = "default" }ou funções auxiliares. Escolha linguagens com??quando precisar frequentemente de valores padrão seguros; em Go, encapsule os padrões em pequenas funções auxiliares. -
Linguagens que eliminam null -- Rust não tem null; valores opcionais usam
Option<T>. Swift e Zig usam tipos opcionais como mecanismo principal: em Swift,String?é distinto deString, e o desempacotamento com!ouif leté explícito. O?Tdo Zig forçaorelseouifpara tratar null. Isso evita o "erro de um bilhão de dólares" das desreferências nulas. Python, JavaScript, Java e C permitemnull/None/undefinedem qualquer lugar, levando aNullPointerExceptionouTypeErrorem tempo de execução. Se eliminar bugs de null é prioridade, escolha Rust; Swift e Zig oferecem tratamento forte de opcionais com menos rigidez que Rust. -
Navegação segura -- Encadeamento opcional (
?.) em JavaScript, PHP, C#, Swift e Ruby previne erros de desreferência nula ao percorrer estruturas aninhadas. Por exemplo,user?.address?.cityretornaundefinedounilse qualquer elo for null em vez de lançar exceção. Oarr?.[0]efunc?.()do JavaScript estendem isso para arrays e chamadas. Sem?., você escreveuser && user.address && user.address.city(JavaScript) ou verificaçõesifaninhadas. Go não tem?.; você verificaif user != nil && user.Address != nil { city = user.Address.City }. Prefira?.ao trabalhar com dados profundamente aninhados e fracamente tipados (APIs, configurações). -
Atribuição com coalescência nula -- JavaScript (
x ??= "default"), PHP ($name ??= "default") e C# (name ??= "default") atribuem um padrão apenas quando a variável é null ou undefined. Isso mantém o código conciso ao inicializar campos opcionais. Go e Rust exigemifoumatchexplícitos. Use??=quando tiver configuração opcional ou padrões lazy; em Go e Rust, use padrõesOption/ifpara maior clareza.