Pular para o conteúdo

Guards e retorno antecipado

Nesta página

Retornar cedo quando uma pré-condição falha é a forma mais legível de validação em Zolo: verifique no topo, retorne Result.Err(...) imediatamente, e o resto da função executa apenas no caminho feliz — sem if/else aninhado.

Combinado com ?, um pipeline de validações fica linear:

Validação com retorno antecipado, pipeline com ?, e opcional com 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)

O padrão create_user mostra o núcleo da técnica: cada validador retorna Result<_, str>, e ? propaga o primeiro erro encontrado sem acumular match aninhado. O chamador vê um único ponto de falha descritivo.

Use guards para invariantes de entrada — dados que chegam do mundo externo (formulários, APIs, arquivos). Dentro da lógica interna, onde você controla os tipos, prefira panic para contratos quebrados.

Desafio

Adicione uma terceira validação a create_user que rejeite emails mais longos que 100 caracteres. Teste com uma string longa e verifique que o erro correto é propagado.

Buscar no Zolo

9 resultados

enespt-br