Ta question n’est effectivement pas claire du tout… L’analyse du typage est purement statique, il n’y a aucune notion de "retourner" ou renvoyer qui intervient.
Reprenons ton exemple, légèrement modifié pour donner un type concret à a
immédiatement :
let a = 0u8;
let b = match a {
0 => "null",
_ => "nonzero",
};
Le système de type va attribuer des types à chaque expression dans sa représentation interne, ressemblant par exemple à ça dans un premier temps :
Assign(
lhs = Ident("a", T_a),
rhs = Literal(0, u8));
Assign(
lhs = Ident("b", T_b),
rhs = MatchStatement(
expr = Expr(Ident("a", T_a), T_e),
branches = [
Branch(
pat = Pattern(Literal(0, T_i), T_p1),
val = Expr(Literal("null", &'static str), T_b1)
),
Branch(
pat = BlankPattern,
val = Expr(Literal("nonzero", &'static str), T_b2)
),
],
T_m,
);
Les trucs en T_*
sont les types pas encore connus à ce stade (i.e. qui n’étaient pas explicites). Puis le moteur d’inférence de types (ça peut paraître simple, mais en fait c’est un gros morceau !) construit un ensemble de contraintes que le code nous donne :
T_a < u8;
T_b < T_m;
T_e < T_a;
T_p1 < T_e;
T_e < T_i;
T_m < (T_b1, T_b2);
T_b1 < &'static str;
T_b2 < &'static str;
Où T_1 < T_2
veut dire que T_2
doit être cohérent avec T_1
, autrement dit T_1 == T_2
, ou bien T_2
est un sous-type de T_1
(en Rust, le seul cas de sous-typage est via les lifetimes) ou bien T_2
peut être coercé en T_1
implicitement (e.g. &mut T
peut être coercé en &T
).
Comme tu vois, tout ceci fonctionne purement par un set de contraintes que le code nous donne, la partie difficile étant d’écrire le solveur de contraintes correctement.