Concrete type vs. what?

In Rust a type might have parameters, e.g., struct S<A>(A). This type is not concrete, but S<int> is.

I've scanned carefully through the Rust Book and the Rust Reference but i can't find any standard way to name S<A>(A) with respect to S<int> and viceversa. If the latter is a concrete type, S<A>(A) is the [something] it came from. Just "type" does not work. "Abstract type" is the type of an impl Trait. The expression "generic type" is used in the Rust Book only for parameters. "Parameterized type"? "Type definition"?

Formally S<A>(A) is a type constructor, but this term is never used in the documentation.

My problem is that I wanna be able to say precisely things like "given a concrete type T, if the type parameter Pof its [name I don't know] is satisfied by [saturated by mabye? assigned to?] a concrete type U then...

In this example above T would be S<int>, [name I don't know] would be S<A>(A), P would be A and U would be int.

It this sounds confused is because I can't find the right words.

"Generic type" is correct, although Rust indeed doesn't appear to use that term a lot. The book uses the term a bit carelessly; it should always say "generic parameter" or "generic type parameter" when referring to type parameters. "Parameterized type" is also correct, as is "polymorphic type" (but this is more academic use and can be confused with runtime polymorphism).

Ok, let's say "generic type" is ok (but, source?). The concrete type is a ... Reification? Concretization? Of the generic type? And on the opposite, given a concrete type, how do we can call user-defined generic type it comes from?

Also, type parameters of the generic type are... Filled? Satisfied? Assigned?

A specific concrete parameterization of a generic type is generally called an "instance" of that type, and the action of creating one, "instantiation". But this can be confused with creating a term, i.e. a value, of a type, which is also called an instance and instantiation. I'm not sure if there's an established term for the opposite, except if you're in a context where "type constructor" is understood, I guess you can say "the constructor of Vec<i32> is Vec<_>".

Type parameters are "substituted" or "assigned" I'd say.

:sigh: I'd wish things were so simple—that would have been my first guess.

But "Instance" is a value for a (concrete) type. E.g., in the reference:

New instances of a struct can be constructed with a struct expression.

Rust Book:

To use a struct after we’ve defined it, we create an instance of that struct by specifying concrete values for each of the fields. We create an instance by stating the name of the struct and then add curly brackets containing key: value pairs, where the keys are the names of the fields and the values are the data we want to store in those fields.

I can't find a single instance of "instance" in the reference or in the book with the meaning you propose.

And certainly, given a generic type, we don't want to talk about an instance of an instance of the type...

I think it's important to accept that no matter how "precise" you want to be, you're going to be too imprecise to some (e.g., type theorists) and too precise to others. Even when dealing with something rigorous like homotopy type theory, there is going to be some need to define terms due to a lack of consensus of what certain terms mean; so it's always nice to just define what certain terms mean a priori and not have to worry that much about ambiguity.

For example during my math education, I encountered different professors and textbooks that would define the set of natural numbers to include 0 and almost as many that wouldn't include 0. This was never a problem because the textbook or professor would simply define what the natural numbers were for them, and there was never any issue since you knew the context you were in.

Additionally, you're inevitably going to be communicating in English (or whatever your natural tongue is) which is unavoidably inconsistent; so you must accept who your target audience is and adjust accordingly.

For me as a math person I prefer unifying theories and terminology; thus I would call all of the below type constructors:

struct Foo;
struct Bar<>;
struct Fizz<T>(T);

The first two are nullary type constructors just like foo is a nullary function in fn foo() -> u32 { 0 }. I'd call all of the below "constructed types":

impl Foo {}
impl Foo<>{}
impl Bar{}
impl Bar<> {}
impl Fizz<u32> {}
impl<T> Fizz<T> {}
fn example() {
    // The discarded variable has constructed type `Foo`/`Foo<>`.
    _ = Foo;
    // The discarded variable has constructed type `Foo`/`Foo<>`.
    _ = Foo::<>;
    // The discarded variable has constructed type `Bar`/`Bar<>`.
    _ = Bar;
    // The discarded variable has constructed type `Bar`/`Bar<>`.
    _ = Bar::<>;
    // The discarded variable has constructed type `Fizz<u32>`.
    _ = Fizz(0u32);
    // The discarded variable has constructed type `Fizz<u32>`.
    _ = Fizz::<u32>(0);
}
fn example2<T>(x: T) {
    // The discarded variable has constructed type `Fizz<T>`.
    _ = Fizz(x);
    // The discarded variable has constructed type `Fizz<T>`.
    _ = Fizz::<T>(x);
}

Normally I don't care about distinguishing between Fizz<T> and Fizz<u32> for the same reason I don't care about using different terminology for x and y in:

fn foo(a: u32) {
    let x = 10u32;
    let y = a;
}

Both x and y are simply "u32 variables" (in Rust terms not math terms) even though x has the property that the value is known before runtime or even compilation time. Fortunately in a higher-level context like this, type parameters/variables are truly variables in the math sense (i.e., unknown constants) since things like "interior mutability" don't make sense allowing for simpler/consistent algebraic reasoning. I'd first ask what terminology you'd use to describe x and y above and use that to guide the terminology for something like Fizz<u32> and Fizz<T>.

Things do become more complex when a proper subset of the type arguments are known a priori (e.g., Buzz<T, u32, S>); and it would seem you would need to rely on some notion of partial functions/evaluation.

Similar questions have been asked like this one on "trait constructors". I think in the end though that you shouldn't worry too much about this as you're going to confuse a lot of your audience if you expect them to have finished homotoypy type theory in order to understand your "precise" terms. By all means use some terms that are fairly common in the Rust community, but there will inevitably be some level of "imprecision" and "inconsistency"; and hopefully any confusion that arises from your chosen terminology will be clarified upon additional communication, context, and examples.

The classic name for that is a "type constructor", though I don't think that's common in Rust.

(Basically, S<A> is a "constructor" that you pass i32 as an argument to as the parameter A, which constructs you the type S<i32>. Kinda like how if you have struct S { a: i32 } you call the constructor S { a: 3 } passing the argument 3 to the parameter a to get an instance.)


What's the context in which you're having this naming conundrum?

2 Likes

It's used in development, at least sometimes. The original name for a GAT was ATC.

These both come across for me:

Given a concrete type T, if the type parameter P of its type constructor...

Given a concrete type T, if the type parameter P from the type definition...

is [the concrete type] int

resolves to [the concrete type] int

I think the points about context and communicating in English are relevant. That is, I think I'd have to see a bigger context to have a stronger opinion, and that there is no single best answer on how to phrase things.

1 Like

Thank you for the only useful answer!

So, the context is this paragraph, and in particular the second item of the first list. It is vaguely comprehensible if you've read the code, but I don't think is sufficiently precise to convey the meaning.

In 30 years of writing mathematics and computer science papers I always had as a target that I could be able to write theorems and definitions with very few symbols, just using words, by choosing carefully my definitions. If you can do that, you have have identified the key concepts and given them a name.

So I'm irritated that I can't find terms here that would help me. I agree with you that's entirely sufficient to find some terms that are common enough to make people understand, even if they do not appear in a formal definition somewhere in the Rust Book or Reference. What I want avoid like the plague, however, is to use terms that are defined with a different meaning, as "instance".

Also, this kind of type manipulation will not be familiar to a lot of Rust users, so I need be minimally precise. Context might be unhelpful.

An alternative is to give up with words, and write something like "If T is a concrete type obtained by resolving the type parameters P₀, P₁, P₂, … of a type definition (struct or enum) to concrete types T₀, T₁, T₂, …, then T:DeserType is obtained by resolving each replaceable type parameter Pᵢ with the concrete type Tᵢ:DeserType instead.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.