Pular para o conteúdo

18 · Módulos

C# com extern

Funções tipadas, closures e objetos .NET chamados como código Zolo.

Nesta página

extern usa um provider importado explicitamente. No plugin dotnet, esse provider é csharp. Os parâmetros e o retorno seguem a sintaxe Zolo; o corpo segue a linguagem do provider.

Declare uma função C#, chame-a normalmente e passe uma closure Zolo como callback. Uma função C# também pode devolver uma closure com estado. int preserva os 64 bits; extern csharp async fn retorna um Future que participa do scheduler Zolo e dos combinadores de Promise.

14-extern-csharp.zolo
Playground
// expected:

// 42

// 9223372036854775807

// Zolo

// 11

// 12

// 42

// 40

// 42


use plugin dotnet::{csharp}
use std::async::Promise

extern csharp fn add(a: int, b: int) -> int {
    return a + b;
}

extern csharp fn exact(value: int) -> int {
    return value;
}

extern csharp fn counter(start: int) -> fn() -> int {
    var value = start;
    return () => System.Threading.Interlocked.Increment(ref value);
}

extern csharp fn apply(value: int, transform: fn(int) -> int) -> int {
    return transform(value);
}

extern csharp async fn twice(value: int) -> int {
    await System.Threading.Tasks.Task.Delay(10);
    return value * 2;
}

print(add(20, 22))
print(exact(9223372036854775807))

let original = "  Zolo  "
let clean: str = extern csharp {
    return original.Trim();
}
print(clean)

let next = counter(10)
print(next())
print(next())

print(apply(20) { |value| add(value, 22) })

let results = await Promise.all([twice(20), twice(21)])
print(results[0])
print(results[1])

Um bloco de expressão captura as variáveis Zolo referenciadas pelo corpo C#. A anotação da variável fornece o tipo esperado do resultado:

use plugin dotnet::{csharp}

let original = "  Zolo  "
let clean: str = extern csharp {
    return original.Trim();
}
print(clean)

Para uma função pequena, coloque os imports nativos C# no começo do corpo:

extern csharp fn byte_count(text: str) -> int {
    using System.Text;
    return Encoding.UTF8.GetByteCount(text);
}

Use namespaces, aliases e using static em funções ou blocos de expressão. Cada binding tem seus próprios nomes importados. Os imports vêm antes dos comandos executáveis; using var, using (...) e await using de recursos mantêm o escopo e o descarte normais do C#. O editor entende os imports e o corpo.

22-extern-native-imports.zolo
Playground
// Native imports belong at the start of the foreign fragment.

use plugin dotnet::{csharp}

extern csharp fn byte_count(text: str) -> int {
    using System.Text;
    return Encoding.UTF8.GetByteCount(text);
}

// The same alias can mean something different in another binding.

extern csharp fn alias_bytes(text: str) -> int {
    using Native = System.Text.Encoding;
    return Native.UTF8.GetByteCount(text);
}

extern csharp fn alias_abs(value: int) -> int {
    using Native = System.Math;
    return Native.Abs(value);
}

extern csharp async fn later(value: int) -> int {
    using static System.Math;
    using Tasks = System.Threading.Tasks.Task;
    await Tasks.Delay(1);
    return Abs(value);
}

extern csharp async fn alias_namespace() -> int {
    using System = global::System.Collections.Generic;
    await global::System.Threading.Tasks.Task.Delay(1);
    return new System::List<int> { 42 }[0];
}

// Resource usings stay in the method and dispose at their ordinary scope.

extern csharp fn resource_declaration() -> int {
    using System.IO;
    using var stream = new MemoryStream(new byte[] { 1, 2, 3, 4 });
    return stream.Length;
}

extern csharp fn resource_statement() -> int {
    using Stream = System.IO.MemoryStream;
    using (var stream = new Stream(new byte[] { 1, 2, 3, 4 })) {
        return stream.Length;
    }
}

extern csharp async fn async_resource() -> int {
    using System.IO;
    await using var stream = new MemoryStream(new byte[] { 1, 2, 3, 4 });
    return stream.Length;
}

print(byte_count("Zolo"))
print(alias_bytes("Zolo"))
print(alias_abs(-42))
print(await later(-42))
print(await alias_namespace())
print(resource_declaration())
print(resource_statement())
print(await async_resource())

let original = " Zolo "
let clean: str = extern csharp {
    using Regex = System.Text.RegularExpressions.Regex;
    return Regex.Replace(original, @"\s", "");
}
print(clean)

extern csharp {
    fn grouped(value: int) -> int {
        using static System.Math;
        return Abs(value);
    }
}
print(grouped(-42))

// expected:

// 4

// 4

// 42

// 42

// 42

// 4

// 4

// 4

// Zolo

// 42

extern csharp mod expõe os tipos públicos de um módulo C#. Construtores, métodos e propriedades recebem bindings tipados a partir dos metadados .NET. Os objetos mantêm sua identidade e seu estado; devolvê-los por um método não copia o objeto.

Construa um contador, altere uma propriedade, escolha overloads por tipo e assine um evento com uma closure Zolo. Feche a assinatura quando terminar e chame Dispose para liberar o recurso de forma determinística.

15-extern-objects.zolo
Playground
// expected:

// 5

// 41

// true

// 42

// true

// true

// 43

// number:7

// text:Zolo

// 44

// 44

// 45

// true

// 42

// i32

// i32

// int

// int

// 42

// ok


use plugin dotnet::{csharp}

extern csharp mod counters {
    public sealed class Counter : IDisposable {
        public long Value { get; set; }
        public bool Disposed { get; private set; }
        public event Action<long>? Changed;

        public Counter(long start = 5) { Value = start; }
        public long Next() {
            if (Disposed) throw new ObjectDisposedException(nameof(Counter));
            var value = ++Value;
            Changed?.Invoke(value);
            return value;
        }
        public Counter Self() => this;
        public Counter? Optional(bool present) => present ? this : null;
        public Counter[] Many() => new[] { this };
        public string Format(long value) => "number:" + value;
        public string Format(string value) => "text:" + value;
        public void Dispose() { Disposed = true; Changed = null; }
    }

    public sealed class Keeper {
        private readonly Func<long, long> callback;
        public Keeper(Func<long, long> callback) { this.callback = callback; }
        public long Run() {
            GC.Collect();
            GC.WaitForPendingFinalizers();
            return callback(41);
        }
    }

    public sealed class Pick {
        public string Kind;
        public Pick(int value) { Kind = "i32"; }
        public Pick(long value) { Kind = "int"; }
        public string Get(int value) => "i32";
        public string Get(long value) => "int";
        public static string Static(int value) => "ok";
    }

    public class Base {
        public long Answer() => 42;
    }
    public sealed class Derived : Base { }
}

let counter = counters.Counter()
print(counter.Value)
counter.Value = 40
print(counter.Next())
let same = counter.Self()
print(same == counter)
print(same.Next())
print(counter.Optional(false) == nil)
print(counter.Optional(true) == counter)
print(counter.Many()[0].Next())
print(counter.Format(7))
print(counter.Format("Zolo"))

let subscription = counter.on_Changed(|value: int| print(value))
print(counter.Next())
subscription.close()
print(counter.Next())
counter.Dispose()
print(counter.Disposed)

let keeper = counters.Keeper(|value: int| value + 1)
print(keeper.Run())
let narrow_value: i32 = 1
let narrow = counters.Pick(narrow_value)
print(narrow.Kind)
print(narrow.Get(narrow_value))
let wide = counters.Pick(1)
print(wide.Kind)
print(wide.Get(1))
print(counters.Derived().Answer())
print(counters.Pick.Static(narrow_value))

C# aguarda callbacks que retornam Future, enquanto Zolo aguarda Task .NET. Use try/catch nos dois sentidos e cancele uma operação em andamento com Future.cancel(). No corpo C#, cancellation é o token cooperativo da operação.

16-extern-async.zolo
Playground
// expected:

// 42

// 43

// callback caught

// CLR caught

// true


use plugin dotnet::{csharp}
use std::async::Promise

extern csharp async fn transform(value: int, callback: fn(int) -> Future<int>) -> int {
    return await callback(value);
}

extern csharp async fn fail() -> int {
    await Task.Delay(5);
    throw new InvalidOperationException("outer", new ArgumentException("inner"));
}

extern csharp async fn wait_for_cancel() -> int {
    await Task.Delay(60000, cancellation);
    return 0;
}

async fn increment(value: int) -> int {
    sleep 2ms
    return value + 1
}

async fn reject(value: int) -> int {
    sleep 1ms
    panic("callback failed")
    return value
}

async fn cancel_soon(future: Future<int>) -> bool {
    sleep 5ms
    future.cancel()
    return future.is_done()
}

let values = await Promise.all([
    transform(41, increment),
    transform(42, increment),
])
print(values[0])
print(values[1])

let callback_result = try { await transform(0, reject) } catch error { "callback caught" }
print(callback_result)
let clr_result = try { await fail() } catch error { "CLR caught" }
print(clr_result)

let pending = wait_for_cancel()
print(await cancel_soon(pending))

Arquivos .cs, assemblies .dll e projetos .csproj usam a mesma entrada:

extern csharp from "./Counter.cs" use {Counter}
extern csharp from "./Legacy.dll" use Acme::Tools::{Formatter}
extern csharp from "./Services/Services.csproj" use Acme::Services::{Client}
extern csharp use System::Text::{StringBuilder}

Passe structs como DTOs tipados, preserve arrays vazios e slots nulos, e transfira bytes. Os testes também verificam inteiros de 64 bits e floats.

17-extern-data.zolo
Playground
// expected:

// 9223372036854775807

// true

// 3

// 7

// true

// 9

// 0

// 3

// Zolo

// 42

// Hi Zolo

// Zolo

// true

// Double


use plugin dotnet::{csharp}

// An unrelated generic must not become an invalid DTO in every extern body.

struct Box<T> { value: T }
struct Payload { value: int, optional: int?, items: [int] }

extern csharp {
    fn echo(input: Payload) -> Payload { return input; }
    fn nullable(values: [int?]) -> [int?] { return values; }
    fn empty() -> [int] { return Array.Empty<long>(); }
    fn create_builder() -> Any { return new System.Text.StringBuilder(); }
}

let copy = echo(Payload {
    value: 9223372036854775807,
    optional: nil,
    items: [7, 8, 9],
})
print(copy.value)
print(copy.optional == nil)
print(copy.items.len())
let values = nullable([7, nil, 9])
print(values[0])
print(values[1] == nil)
print(values[2])
print(empty().len())
print(nullable([7, nil, nil]).len())

let original = "Zolo"
let clean: str = extern csharp { return original.Trim(); }
print(clean)
let run = |value: int| extern csharp { return value + 1; }
print(run(41))
let greeting: str = extern csharp { return $"Hi {original}"; }
print(greeting)

// The dynamic API uses a live proxy for types absent from a typed import.

let builder = create_builder()
builder.Append("Zolo")
print(builder.ToString())

extern csharp fn is_negative_zero(value: float) -> bool {
    return double.IsNegativeInfinity(1.0 / value);
}
extern csharp fn clr_type(value: Any) -> str { return value.GetType().Name; }
print(is_negative_zero(-0.0))
print(clr_type(1.0))

Importe código C#, uma DLL, um projeto com dependências e tipos do framework. No build, os artifacts necessários acompanham o executável; os fontes e o SDK não precisam estar presentes na máquina que o executa.

18-extern-imports.zolo
Playground
// expected:

// i32

// i32

// int

// 42

// 42

// 42

// number:7

// text:Zolo

// 42

// 42

// Zolo


use plugin dotnet::{csharp}

extern csharp from "./extern-fixtures/Source.cs" use Demo::Sources::{Calculator}
extern csharp from "./extern-fixtures/Legacy/bin/Release/net8.0/Legacy.dll" use Demo::Legacy::{Formatter}
extern csharp from "./extern-fixtures/Services/Services.csproj" use Demo::Services::{Client}
extern csharp use System::Text::{StringBuilder}

extern csharp fn read(client: Client) -> int { return client.Compute(40); }
extern csharp from "./extern-fixtures/Legacy/bin/Release/net8.0/Legacy.dll" fn number(value: int) -> str = Demo.Legacy.Formatter.Format

let narrow_value: i32 = 41
let narrow = Calculator(narrow_value)
print(narrow.Kind)
print(narrow.Get(narrow_value))
print(narrow.Get(1))
print(narrow.Add(1))
print(await narrow.Later(1))
let work = narrow.CreateWork()
print(await work(1))
print(number(7))
print(Formatter.Format("Zolo"))
let client = Client()
print(client.Compute(40))
print(read(client))
let builder = StringBuilder()
builder.Append("Zolo")
print(builder.ToString())

Para rodar esse exemplo diretamente, compile primeiro a DLL de demonstração: dotnet build examples/features/14-modules/extern-fixtures/Legacy/Legacy.csproj -c Release.

Um timer .NET pode acordar um receiver Zolo parado num channel. Erros nos callbacks passam pelo try/catch; Dispose encerra o recurso e permite detectar um channel sem nenhuma possibilidade de progresso.

19-extern-background.zolo
Playground
// expected:

// 42

// 43

// timer finished

// 42

// 42

// timer caught

// disposed timer

// 42


use plugin dotnet::{csharp}

// The CLR retains these callbacks after start() returns. Their bodies still

// execute on the owning Zolo runtime thread, including the async callback.

extern csharp fn start(callback: fn(int) -> nil) -> Any {
    return new System.Threading.Timer(_ => callback(42), null, 20, -1);
}
extern csharp fn start_async(callback: fn(int) -> Future<int>) -> Any {
    return new System.Threading.Timer(async _ => await callback(42), null, 40, -1);
}
async fn increment(value: int) -> int {
    sleep 1ms
    print(value + 1)
    return value + 1
}

let first = start(|value| { print(value) })
let second = start_async(increment)
sleep 100ms
first.Dispose()
second.Dispose()
print("timer finished")

// A retained CLR callback is a possible waker even when all Zolo tasks

// are parked on a channel and there are no Zolo timers left to run.

let scoped = channel(1)
let scoped_timer = start(|value| { scoped.send(value) })
scope {
    spawn { print(scoped.recv().unwrap()) }
}
scoped_timer.Dispose()

let global = channel(1)
let global_timer = start(|value| { global.send(value) })
async fn receive() -> int { return global.recv().unwrap() }
print(await receive())
global_timer.Dispose()

let failed = channel(1)
let failed_timer = start(|_value| { panic("background callback failed") })
let result = try {
    scope { spawn { failed.recv().unwrap() } }
    "unexpected success"
} catch error { "timer caught" }
print(result)
failed_timer.Dispose()
// Disposed CLR resources retain their delegates for GC, but cannot wake a

// parked task. This must diagnose a deadlock and leave the scheduler usable.

let disposed_timer = start(|_value| {})
disposed_timer.Dispose()
let abandoned = channel(1)
let disposed_result = try {
    scope { spawn { abandoned.recv().unwrap() } }
    "unexpected success"
} catch error { "disposed timer" }
print(disposed_result)
async fn recover() -> int { sleep 1ms; return 42 }
print(await recover())

Exporte um tipo .NET por um arquivo Zolo e importe-o com um alias em outro. Construtores, factories, propriedades e overloads continuam tipados.

21-extern-module-imports.zolo
Playground
// expected:

// zero

// i32

// i32

// int

// text:Zolo

// static:i32

// static:int

// str-Any

// str-Any

// text:again

// text:factory

// i32


use extern_fixtures::api::{self as tools, Pick as Counter, make}

let zero = Counter()
print(zero.Kind)
let narrow_value: i32 = 1
let narrow = Counter(narrow_value)
print(narrow.Kind)
print(narrow.Get(narrow_value))
print(narrow.Get(1))
print(narrow.Get("Zolo"))
print(Counter.Static(value: narrow_value))
print(Counter.Static(1))
let dynamic: Any = 7
print(narrow.Named(y: dynamic, x: "Zolo"))
print(narrow.Named("Zolo", dynamic))
print(narrow.Self().Get("again"))
let created = make()
print(created.Get("factory"))
let qualified = tools.Pick(narrow_value)
print(qualified.Get(narrow_value))

O editor analisa o corpo C# com Roslyn, mostrando erros no arquivo original, completion e hover. O plugin requer .NET 8 ou superior. Projetos precisam do SDK no build; dependências NuGet devem ter packages.lock.json. VM, native e LLVM suportam esse provider. Um build WebAssembly sem host .NET informa que o provider não está disponível.

Índice global

Encontre seu caminho pela Zolo

Experimente uma ideia

Comece por aqui

9 resultados

9 resultados

enpt-br