Saltar al contenido

Guards y retorno anticipado

En esta página

Retornar anticipadamente cuando una precondición falla es la forma más legible de validación en Zolo: verifica al inicio, retorna Result.Err(...) de inmediato, y el resto de la función se ejecuta solo en el camino feliz — sin if/else anidado.

Combinado con ?, un pipeline de validaciones queda lineal:

Validación con retorno anticipado, pipeline con ?, y opcional con retorno nil.

09-guards-and-early-return.zolo
Playground
// Feature: Early-return and argument validation

// Syntax: `if cond { return ... }` at the start of the function

// When to use: validate preconditions and simplify the happy path.


use std::Result

// -- Validation as Result — no panic ---------------------------

fn validate_age(n: int) -> Result<int, str> {
  if n < 0 {
    return Result.Err("negative age")
  }

  if n > 150 {
    return Result.Err("implausible age")
  }

  return Result.Ok(n)
}

fn describe(r: Result<int, str>) {
  match r {
    Result::Ok(v) => print("ok: {v}"),
    Result::Err(e) => print("error: {e}"),
  }
}

describe(validate_age(30))  // ok: 30

describe(validate_age(-1))  // error: negative age

describe(validate_age(200))  // error: implausible age


// -- Validation pipeline with `?` -----------------------------

fn validate_email(s: str) -> Result<str, str> {
  if s.len() == 0 {
    return Result.Err("empty email")
  }

  if !s.contains("@") {
    return Result.Err("email missing @")
  }

  return Result.Ok(s)
}

fn validate_name(s: str) -> Result<str, str> {
  if s.len() < 2 {
    return Result.Err("name too short")
  }

  return Result.Ok(s)
}

struct User {
  name: str,
  email: str,
}

fn create_user(name: str, email: str) -> Result<User, str> {
  let n = validate_name(name)?  // propagates error

  let e = validate_email(email)?  // propagates error

  return Result.Ok(User { name: n, email: e })
}

fn describe_user(r: Result<User, str>) {
  match r {
    Result::Ok(u) => print("user: {u.name} <{u.email}>"),
    Result::Err(msg) => print("error: {msg}"),
  }
}

describe_user(create_user("Alice", "alice@x.com"))

// expected: user: Alice <alice@x.com>

describe_user(create_user("A", "alice@x.com"))

// expected: error: name too short

describe_user(create_user("Alice", "invalid"))

// expected: error: email missing @


// -- Optional + early return ----------------------------------

fn first_vowel(s: str) -> str? {
  for c in s.chars() {
    if c == "a" || c == "e" || c == "i" || c == "o" || c == "u" {
      return c
    }
  }

  return nil
}

print(first_vowel("brown") ?? "(none)")  // o

print(first_vowel("xyz") ?? "(none)")  // (none)

El patrón create_user muestra el núcleo de la técnica: cada validador retorna Result<_, str>, y ? propaga el primer error encontrado sin acumular match anidado. El llamador ve un único punto de fallo descriptivo.

Usa guards para invariantes de entrada — datos que llegan del mundo externo (formularios, APIs, archivos). Dentro de la lógica interna, donde controlas los tipos, prefiere panic para contratos rotos.

Desafío

Agrega una tercera validación a create_user que rechace emails de más de 100 caracteres. Prueba con una cadena larga y verifica que el error correcto se propaga.

Buscar en Zolo

9 resultados

enespt-br