Cópia estática
Copiado de docs/manual/interfaces-and-contracts.md em https://github.com/poppy-team/aipo-lang (MIT). Fixado na revisão ce47c4e070404fc41722ccd8e2b7b808b99062a8, blob 106c7e1fd429451c27f7c36500b398b03ae9cf08. O repositório de origem permanece canônico; esta cópia é atualizada por um pull request de sincronização, não em tempo real.
O sistema de tipos do Aipo une a ergonomia da tipagem dinâmica com a precisão dos contratos de assinatura, invariantes de dados e interfaces com subtipagem estrutural automática.
struct) Estruturas agregam campos nomeados e são delimitadas por chaves { ... }.
Por padrão de design seguro e previsível, todos os campos de uma estrutura são imutáveis. Quando for necessário permitir mutação de um campo durante o ciclo de vida da instância, declare-o explicitamente com a palavra-chave var:
struct Servidor {
id
criado_em
var status = "offline"
var carga = 0.0
}
# Instanciação estrutural usando chave-valor simétrico com ':'
let s = Servidor{
id: "srv-1",
criado_em: 1600000000,
status: "online",
carga: 0.42,
}
io.println(s.id) # "srv-1"
io.println(s.status) # "online"Tentar reatribuir um campo imutável após a construção da instância dispara o diagnóstico semântico estático AIPO_SEM_IMMUTABLE_FIELD_REASSIGN.
init) O hook init se associa ao tipo pelo prefixo Tipo: e permite validar, transformar e inicializar os campos da instância antes de sua publicação final:
struct Usuario {
email
nome
}
Usuario:init(email, nome) {
if not email.contains("@") {
return fail("Formato de e-mail inválido")
}
self.email = email
self.nome = nome
}
let u = Usuario{ email: "user@example.com", nome: "Dev" }
io.println(u.email) # "user@example.com"invariant) As invariantes declaram predicados lógicos em Tipo:invariant que devem permanecer verdadeiros durante todo o ciclo de vida do objeto:
struct Intervalo {
var inicio = 0
var fim = 0
}
Intervalo:init(inicio, fim) {
self.inicio = inicio
self.fim = fim
}
Intervalo:invariant {
self.inicio <= self.fim
}
let inter = Intervalo{ inicio: 5, fim: 10 }
io.println(inter.inicio) # 5
io.println(inter.fim) # 10Sempre que um campo de uma estrutura com bloco invariant for alterado, o motor de execução verifica automaticamente o predicado. Caso a verificação falhe, a operação é rejeitada. Se estiver dentro de um bloco attempt { ... }, as mutações anteriores sofrem rollback automático pelo journal transacional.
var self) Métodos são associados a um tipo pelo prefixo Tipo:. O receptor self é implícito em métodos imutáveis e explícito como var self em métodos que mutam.
Por padrão de segurança, o receptor self é somente leitura. Quando um método precisa alterar o estado interno da instância, ele declara explicitamente var self, alinhando a mutabilidade de métodos à mesma regra universal de variáveis da linguagem:
struct Contador {
var valor = 0
}
# Método de leitura: self é implícito e imutável
Contador:atual() -> Int {
return self.valor
}
# Método mutador: var self declara explicitamente a intenção de modificar
Contador:incrementar(var self) {
self.valor += 1
}interface) Interfaces declaram contratos estruturais de métodos. Em Aipo, conformidade não exige declarações burocráticas no topo do arquivo: a subtipagem é estrutural e automática (modelo inspirado em linguagens modernas de alta produtividade como Go e Luau).
Se uma estrutura implementa todos os métodos exigidos por uma interface com assinaturas e contratos compatíveis (incluindo aridade e mutabilidade de self), ela automaticamente satisfaz a interface, sem necessidade de nenhum comando adicional:
interface Renderizavel {
desenhar() -> String
}
#!satisfies Renderizavel
struct Botao {
texto
}
Botao:desenhar() -> String {
return f"[Botão: {self.texto}]"
}
# Botao satisfaz Renderizavel por correspondência estrutural de métodos.
# A diretiva #!satisfies exige essa relação e verifica em tempo de compilação;
# sem ela, a correspondência continua válida — a diretiva apenas documenta
# a intenção e falha se a conformidade deixar de valer.
# Aceita qualquer valor que satisfaça a interface Renderizavel
fn renderizar_elemento(item: Renderizavel) -> String {
return item.desenhar()
}
let btn = Botao{ texto: "Salvar" }
io.println(renderizar_elemento(btn)) # "[Botão: Salvar]"A conformidade é verificada estaticamente pelo analisador semântico (aipo-sema), checando a existência dos métodos, número de argumentos, tipos de parâmetros e retornos, e a mutabilidade compatível do receptor (self vs var self).
::) Funções livres existentes podem ser promovidas em lote para métodos associados a um tipo através do token :::
fn perimetro(self) -> Float {
return 2.0 * (self.largura + self.altura)
}
fn duplicar(var self) {
self.largura *= 2.0
self.altura *= 2.0
}
# Promove ambas as funções para métodos de Retangulo
Retangulo::[perimetro, duplicar]| Regra | Detalhe |
|---|---|
| Primeiro Parâmetro | Obrigatoriamente self ou var self. Funções sem self como primeiro parâmetro são rejeitadas com erro estático pelo compilador. |
| Mutabilidade | Preservada da origem: self gera método somente leitura; var self gera método mutador. |
| Acesso | A função original continua acessível livremente (perimetro(ret)) e como método (ret.perimetro()). |
enum) O enum representa tipos algébricos fechados ("isto é A ou B"), onde o conjunto de valores possíveis é conhecido em tempo de compilação:
enum EstadoConexao {
Desconectado,
Tentando(tentativa: Int),
Conectado { ip: String, ping_ms: Int },
Erro(mensagem: String),
}Desconectado).Tentando(tentativa: Int)).Conectado { ip: String, ping_ms: Int }).A construção de um enum qualifica o tipo diretamente (EstadoConexao.Desconectado), sem new ou ::.
O match sobre enum é verificado por exaustividade: se não houver um braço else, todas as variantes devem ser cobertas obrigatoriamente:
fn relatar(estado: EstadoConexao) -> String {
return match estado {
when EstadoConexao.Desconectado {
"Sem conexão"
}
when EstadoConexao.Tentando(t) {
f"Tentativa #{t}"
}
when EstadoConexao.Conectado { ip, ping_ms } {
f"Conectado a {ip} ({ping_ms}ms)"
}
when EstadoConexao.Erro(motivo) {
f"Falha: {motivo}"
}
}
}Enums também suportam métodos (EstadoConexao:metodo()), invariantes (EstadoConexao:invariant) e diretiva de interface #!satisfies.
#!nome) Diretivas são comentários estruturados lidos diretamente pelo compilador que se aplicam ao item imediatamente seguinte:
| Diretiva | Aplicação | Efeito |
|---|---|---|
#!satisfies I1, I2 | struct ou enum | Exige conformidade estrutural com as interfaces em tempo de compilação. |
#!test / #!test[tag] / #!test("nome") | fn livre | Marca a função como teste unitário descoberto pelo runner de testes. |
#!deprecated("msg") | fn, struct ou método | Emite avisos durante a checagem semântica indicando obsolescência. |
#!todo("msg") | qualquer item | Rastreia débitos técnicos sem interromper o fluxo de compilação. |