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ções describing(...) / reflecting(...) com a mesma ordem de prioridade.

Decisões de fidelidade

  • Equatable ≠ Hashable: como no Swift, Equatable fornece só ==. Quem dá hash (e habilita Set/Map) é Hashable.
  • Comparable: só < é exigido; os demais operadores derivam exatamente como no Swift (a <= b ≡ !(b < a)). O nome Comparable sombreia o do dart:core de propósito — e conformamos a ele, então List.sort funciona.
  • Optional: o force-unwrap (unwrapped) lança OptionalUnwrapError, espelhando o trap de nil!.
  • ~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=1 ajuda) — ou (2) worker/CLI de lote (import/export/ ETL) e compute pesado (imagem, cripto). E nesse hot path use dart: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