Variance of extern fn vs. fn

Hi

I'm preparing a presentation on subtyping and variance and wanted to check that given that the normal fn(T) -> U is covariant wrt U and contravariant wrt T, and because extern types are bivariant, then why extern fn(T) -> U preserves that variance wrt U, T?
shouldn't xform be applied? is extern a form of type composition or type aggregate? or why GLB is applied?

1 Like

Why would changing the calling convention on a function change its variance w.r.t. the parameters?

It shouldn't! My question is about xform vs glb application. Glb is about collection type (struct, enum etc) and I don't understand why extern should behave like that. More fundamental I guess!

Maybe ask on https://internals.rust-lang.org/ instead?

I wouldn't. This question seems appropriate for URLO. It's not a proposal to change the language, after all.

I think that's where your mistake is coming from. There's no such thing as an "extern type constructor". "extern" isn't a type modifier. "extern fn" is a type constructor all its own, separate from "fn" though extremely similar to it, and separate from "extern type".

“extern fn” is a type constructor all its own, separate from “fn” though extremely similar to it, and separate from “extern type”

Could you elaborate and show proof in compiler (pointing to code in rustc)? what's "extern type" then, if different from extern fn?

Sure, let's dive into TyKind, the high-level intermediate representation of all of Rust's types.

The extern type that the blog post about variance was talking about, has TyKind::Foreign, added in this pull request.

extern fn, however, is implemented by producing a TyKind::FnPtr with the appropriate ABI in its signature.

They both use the extern keyword because they're both used for FFI. Other than that, they're unrelated.

4 Likes

Great! thank you. This is what I was looking for :slight_smile:

So to sum up, it proves that extern fn (as well as fn and not all builtin primitive are bivariant) is a type aggregator (while "extern type" Foreign(DefId) is plain bivariant) because of

  1. FnPtr(PolyFnSig<'tcx>)
  2. type PolyFnSig<'tcx> = Binder<FnSig<'tcx>>
pub struct FnSig<'tcx> {
    pub inputs_and_output: &'tcx List<Ty<'tcx>>,
    pub variadic: bool,
    pub unsafety: hir::Unsafety,
    pub abi: abi::Abi,
}

so glb is applied internally and proves the variance by respecting variances of arguments and output types.

Btw, Foreign(DefId) is the only enum variant with no doc!

1 Like