105 / Types and patterns

An Fn field in a struct

A struct can carry a field of type Fn, and calling it works with three kinds of receiver: a local variable, a by-value parameter, and a field nested inside another struct. What still does not work is calling the field directly (pais.calcular_impuesto(monto)): the compiler rejects that with NYX1016, so the field has to be bound to a variable first, without annotating its type.

105-struct-fn-field.nxSource →
// Campo `Fn` en un struct: ligar el campo a una variable SIN anotar el tipo
// y llamar la variable — funciona con el receptor como variable local,
// parámetro por valor y campo anidado (también con parámetro puntero,
// `c: *Contrato`, no mostrado aquí). Llamar el campo DIRECTO
// (`pais.calcular_impuesto(monto)`) sigue sin soportarse (NYX1016): hay que
// ligarlo a una variable primero.

struct Pais {
    nombre: String,
    calcular_impuesto: Fn(float) -> float
}

struct Region {
    pais: Pais
}

fn impuesto_ar(monto: float) -> float {
    return monto * 0.21
}

fn impuesto_uy(monto: float) -> float {
    return monto * 0.22
}

// Receptor: parámetro POR VALOR
fn cobrar(p: Pais, monto: float) -> float {
    let calcular = p.calcular_impuesto   // sin anotar
    return calcular(monto)
}

fn main() -> int {
    let ar: Pais = Pais { nombre: "Argentina", calcular_impuesto: impuesto_ar }

    // Receptor: variable local
    let f = ar.calcular_impuesto
    print(ar.nombre + ": " + float_to_string(f(100.0)))

    print("cobrar(): " + float_to_string(cobrar(ar, 100.0)))

    // Receptor: campo anidado
    let uy: Pais = Pais { nombre: "Uruguay", calcular_impuesto: impuesto_uy }
    let r: Region = Region { pais: uy }
    let calcular_anidado = r.pais.calcular_impuesto
    print(r.pais.nombre + ": " + float_to_string(calcular_anidado(100.0)))

    return 0
}
Outputstdout
Argentina: 21.0
cobrar(): 21.0
Uruguay: 22.0

How it works

All three calls follow the same pattern: let calcular = valor.campo_fn followed by calcular(argumentos). f = ar.calcular_impuesto binds the field off a local variable; cobrar does the same off p, a by-value parameter; and calcular_anidado = r.pais.calcular_impuesto walks a struct field nested inside another struct before binding.

None of the three annotates the type of calcular: the compiler infers the full signature (Fn(float) -> float) from the source field, so the indirect call knows which return convention to use. That is exactly what avoids the unsigned-Fn problem — see the HTTP deadline-and-cause recipe for the case where a signature does have to be declared by hand.