acdg_foundations
Recriação fiel — em Dart puro, sem nenhuma dependência — dos protocolos e tipos de valor fundamentais da stdlib do Swift. A meta é preservar a intenção e a consequência prática de cada construção, não só o nome.
| Swift | Aqui | O que você implementa | O que ganha de graça |
|---|---|---|---|
Optional<Wrapped> |
Optional (Some/None) |
— | map, flatMap, or/orElse (??), unwrapped (! com trap), match, ifSome, toNullable |
Equatable |
mixin Equatable |
isEqual |
operator == (relação de equivalência) |
Hashable: Equatable |
mixin Hashable on Equatable |
hash(Hasher) |
hashValue, hashCode, chave de Set/Map |
Comparable: Equatable |
mixin Comparable<T> on Equatable |
isLessThan |
<, <=, >, >=, compareTo (List.sort) |
Identifiable |
mixin Identifiable<ID> |
id |
identidade estável; + ObjectIdentifier, ReferenceIdentifiable |
Copyable |
interface Copyable<Self> |
copy() |
contrato de cópia independente |
~Copyable (move-only) |
base NonCopyable |
(opcional) onConsume |
borrow/consume/move com trap em use-after-consume |
BitwiseCopyable |
mixin BitwiseCopyable |
— | marcador de tipo trivial (memcpy) |
Escapable |
mixin Escapable |
— | marcador |
~Escapable (lifetime) |
base NonEscapable + borrowing(...) |
guard ensureInScope() |
trap se o valor "escapar" do escopo |
CustomStringConvertible |
mixin CustomStringConvertible |
description |
toString() delega para description |
CustomDebugStringConvertible |
mixin CustomDebugStringConvertible |
debugDescription |
usado por reflecting(...) |
LosslessStringConvertible |
mixin … on CustomStringConvertible + LosslessStringFactory |
description + call(String) |
round-trip T(x.description) == x |
CaseIterable |
mixin CaseIterable<Self> |
allCases |
em enums, equivale ao values nativo |
RawRepresentable |
mixin RawRepresentable<RawValue> + RawRepresentableFactory |
rawValue + call(raw) |
round-trip T(rawValue: x.rawValue) == x |
Os "init falíveis" do Swift (
init?(_:),init?(rawValue:)) viram objetos-fábrica invocáveis (LosslessStringFactory,RawRepresentableFactory), já que o Dart não permite exigir construtores em interfaces.String(describing:)/String(reflecting:)viram as funçõesdescribing(...)/reflecting(...)com a mesma ordem de prioridade.
Decisões de fidelidade
Equatable≠Hashable: como no Swift,Equatablefornece só==. Quem dá hash (e habilitaSet/Map) éHashable.Comparable: só<é exigido; os demais operadores derivam exatamente como no Swift (a <= b ≡ !(b < a)). O nomeComparablesombreia o dodart:corede propósito — e conformamos a ele, entãoList.sortfunciona.Optional: o force-unwrap (unwrapped) lançaOptionalUnwrapError, espelhando o trap denil!.~Copyable/~Escapable: o Dart não tem move semantics nem lifetimes, então emulamos a consequência em tempo de execução (o compilador Swift faz em tempo de compilação): usar um valor consumido/escapado lança erro.
Initialization with Literals (ExpressibleBy*Literal)
Os protocolos que, no Swift, deixam o compilador converter um literal diretamente
num tipo custom (let c: Celsius = 37.5). O Dart não tem conversão de literal
definível pelo usuário, então — como nos outros init — eles são reificados
como fábricas, preservando a consequência (construir o tipo a partir do literal):
final class CelsiusLiteral
implements ExpressibleByIntegerLiteral<Celsius>, ExpressibleByFloatLiteral<Celsius> {
const CelsiusLiteral();
@override Celsius integerLiteral(int v) => Celsius(v.toDouble());
@override Celsius floatLiteral(double v) => Celsius(v);
}
const CelsiusLiteral().floatLiteral(37.5); // Celsius(37.5)
Inclui a hierarquia de string (…UnicodeScalarLiteral ← …ExtendedGraphemeClusterLiteral
← ExpressibleByStringLiteral ← ExpressibleByStringInterpolation), o motor de
interpolação (StringInterpolationProtocol / DefaultStringInterpolation) que você
dirige na mão como o compilador faz com "Olá \(nome)!", e StaticBigInt
(literais inteiros de precisão arbitrária).
Encoding / Decoding (Codable)
A arquitetura inteira do Codable do Swift: Encodable/Decodable/Codable,
Encoder/Decoder, os containers (keyed/unkeyed/single-value, com o split
…Protocol + box), CodingKey & cia., e EncodingError/DecodingError com
codingPath. Vem com coders concretos JSONEncoder/JSONDecoder e um
JSONSerialization (writer + parser) escrito do zero, sem dart:convert.
enum UserKey implements CodingKey {
id('id'), fullName('full_name'); // mapeia para chaves customizadas
const UserKey(this.stringValue);
@override final String stringValue;
@override int? get intValue => null;
}
final class User implements Codable {
const User(this.id, this.fullName);
final int id;
final String fullName;
@override
void encode(Encoder encoder) {
final c = encoder.container<UserKey>();
c.encode(id, forKey: UserKey.id);
c.encode(fullName, forKey: UserKey.fullName);
}
// `init(from:)` reificado como fábrica (Dart não exige construtores em interface):
static const Decodable<User> decoder = _UserDecoder();
}
final json = const JSONEncoder().encodeToString(const User(7, 'Gabriel'));
// {"id":7,"full_name":"Gabriel"}
final user = const JSONDecoder().decodeFromString(User.decoder, json);
Adaptações fiéis às limitações do Dart: init(from:)/init?(...) viram
fábricas reificadas (Decodable<T>); sem overload por tipo, encode recebe
Object? e o decode usa primitivos tipados (decodeInt/decodeString…) + o
genérico decode<T>(Decodable<T>, forKey:) (a fábrica no lugar do T.self).
Optional Binding (if let / guard let / switch)
As estruturas de controle que vinculam condicionalmente o valor desempacotado de
um Optional:
// if let value = opt { ... } else { ... }
opt.ifLet((v) => use(v), orElse: () => semValor());
// if let value = opt, where(value) { ... }
opt.ifLet((v) => use(v), where: (v) => v > 5);
// guard let value = opt else { <SAI do escopo> }
final v = opt.guardLet(orElse: () => throw ArgumentError('nil'));
// `v` é não-opcional daqui pra baixo.
// guard let a = optA, let b = optB else { return fallback }
final r = binding<int>(
(let) => let(optA) + let(optB), // qualquer None → orElse
orElse: () => 0,
);
// switch opt { case .some(let v): ...; case .none: ... } — nativo do Dart
final texto = switch (opt) {
Some(:final value) => 'some($value)',
None() => 'none',
};
O detalhe mais fiel é o guard let: o orElse tem tipo Never Function(),
então o compilador exige que esse ramo não retorne (precisa throw/sair) —
exatamente como o else de um guard no Swift é obrigado a deixar o escopo.
Tentar orElse: () => 0 é erro de compilação
(return_of_invalid_type_from_closure).
Performance e quando usar (vs Dart canônico)
Comparação honesta, com benchmarks medidos (Apple M2, AOT). O Dart canônico é mais rápido em quase tudo; a vantagem desta lib é ergonomia/explicitude.
| Cenário | Canônico | acdg_foundations |
Razão |
|---|---|---|---|
Igualdade + Set |
~103 µs | ~130 µs | 1,3× mais lento |
Optional vs nullable |
~0,9 µs | ~37 µs | ~41× mais lento (boxing) |
| JSON encode | ~42 µs | ~81 µs | 1,9× mais lento |
| JSON decode | ~22 µs | ~60 µs | 2,6× mais lento |
Sort Comparable |
~313 µs | ~214 µs | 1,5× mais rápido |
Há ainda benchmarks de alocação/GC, payloads grandes e paralelismo com
isolates (com testes de corretude em test/concurrency_test.dart provando que
os value types são "Sendable" entre isolates). Resumo: o Optional troca memória
- GC por expressividade (evite em coleções grandes/loops quentes); isolates dão speedup real porém sublinear (limitado pela cópia de mensagem).
Regra de ouro: acdg_foundations para expressividade na borda do domínio;
Dart canônico (T?, dart:convert) para performance no núcleo e hot paths.
Detalhes, prós/contras por recurso e o guia completo em
doc/comparativo-dart-canonico.md. Rode os
benchmarks:
dart compile exe benchmark/comparison_benchmark.dart -o /tmp/bench && /tmp/bench
dart compile exe benchmark/allocation_benchmark.dart -o /tmp/alloc && /tmp/alloc
dart compile exe benchmark/concurrency_benchmark.dart -o /tmp/conc && /tmp/conc
🧭 Isolates valem a pena num app comum? Quase nunca. Payloads pequenos decodificam em <2 ms e o spawn + cópia de mensagem custa mais que o trabalho. Vá para isolate (via
compute()no Flutter) só quando: (1) Flutter com payload grande travando a UI — o ganho aqui é responsividade, não throughput (atéK=1ajuda) — ou (2) worker/CLI de lote (import/export/ ETL) e compute pesado (imagem, cripto). E nesse hot path usedart:convert, não esta lib. Detalhes na nota completa do doc.
Como usar em outro projeto
Este package não está publicado no pub.dev. Importe por caminho ou git no
pubspec.yaml do projeto consumidor:
dependencies:
acdg_foundations:
path: ../acdg-foundations # caminho local
# ou:
# git:
# url: https://github.com/acdgbrasil/acdg-foundations.git
import 'package:acdg_foundations/acdg_foundations.dart';
final class Money with Equatable, Hashable, Comparable<Money> {
const Money(this.cents);
final int cents;
@override
bool isEqual(Money other) => cents == other.cents;
@override
void hash(Hasher hasher) => hasher.combine(cents);
@override
bool isLessThan(Money other) => cents < other.cents;
}
void main() {
final precos = <Money>{const Money(990), const Money(990)}; // Hashable → dedup
print(precos.length); // 1
final ordenado = [const Money(3), const Money(1), const Money(2)]..sort();
print(ordenado.map((m) => m.cents)); // (1, 2, 3)
print(const Optional.some(21).map((v) => v * 2).or(0)); // 42
}
Veja example/acdg_foundations_example.dart para os sete em ação.
Desenvolvimento
dart pub get
dart analyze
dart test
dart run example/acdg_foundations_example.dart
Licença
BSD-3-Clause © 2026 ACDG Technology.
Libraries
- acdg_foundations
acdg_foundations