Hacker News new | past | comments | ask | show | jobs | submit login

Can you clarify why you don't think Rust has parametric polymorphism?

  struct Person<T> {
    age: T,
  }
Is parametric polymorphism and valid Rust.



Just because some bounds are parametric does not mean that all of them are; specialization violates parametricity, for example. While that's not in Rust proper yet, it did require making sure that dropck could handle it, for example, and the intention is to ship it.

There's also more minor things like https://github.com/rust-lang/rfcs/pull/1210#issuecomment-179...


I don't doubt you know more about Rust than I do, but this seems pedantic to me. Kind of like correcting someone for pronouncing forte as "for-TAY" instead of "fort" or telling someone "well technically you don't actually touch _anything_ because of the electrostatic force."

If you ask all the developers out there to describe parametric and ad-hoc polymorphism I think a vast majority would give the example of a type parameter (e.g., Java generics or C++ templates) for parametric polymorphism and Java interfaces or Haskell's classes for ad-hoc polymorphism. I can even quote directly from Pierce (Types and Programming Languages):

> Parametric polymorphism, the topic of this chapter, allows a single piece of code to be typed "generically," using variables in place of actual types, and then instantiated with particular types as needed. Parametric definitions are uniform: all of their instances behave the same.

I think Rust and the aforementioned languages fit this definition. Outside of a specific compiler issue, claiming otherwise seems to only confuse the issue, especially for those just casually reading and not familiar with programming language theory.


> If you ask all the developers out there to describe parametric and ad-hoc polymorphism I think a vast majority would give the example of a type parameter (e.g., Java generics or C++ templates) for parametric polymorphism and Java interfaces or Haskell's classes for ad-hoc polymorphism. I can even quote directly from Pierce (Types and Programming Languages):

In practice it is often useful to drop our demands for rigour by a bit. But any reasonable definition of parametric polymorphism _has_ to exclude C++ templates.

C++ templates are much closer to the definition of ad-hoc polymorphism.

My practical less-than-rigorous rule-of-thumb for parametric polymorphism amounts to something like: whenever Wadler's Theorems for Free paper applies. https://ttic.uchicago.edu/~dreyer/course/papers/wadler.pdf

Theorems for Free means that eg the type of the identity function (forall a . a -> a) guarantees that even if you supply it a bool, it can't magically turn into an implementation for `not`, or multiply all integers by 2.


This isn't Rust specific; it's just the definition of parametric polymorphism. Yes, many programmers may give you a slightly incorrect definition, but especially in an article about Haskell, I'd expect a bit more precision.

Which doesn't mean it's terrible to get it wrong, just want to be clear about what Rust does and does not have. It is important because these kinds of definitions either hold or they don't; "sorta kinda mostly parametric" isn't the way folks tend to think about this. Which makes sense, because they're interested in proofs and formal definitions.

Yes, Pierce is great! But the issue is:

> all of their instances behave the same.

This is not true in Rust, as shown in my comment and the other replies. We have accepted APIs that break this property, and we have other language features on the way that break this property.


IIRC, Java interfaces are subtype polymorphism. Ad hoc polymorphism would be, for example, overloading.


In a sense even things like `TypeId::of` or `mem::size_of` violate parametricity.


You can do the following, which violates strict parametricity:

    fn foo<T>() -> usize {
        std::mem::size_of::<T>()
    }

    foo::<u8>(); // 1
    foo::<u16>();  // 2


You can do the same in Java and C++. This may violate a strict definition of parametricity (I've read the definition from a few different sources and am still mulling it over), but I'm not sure how this relates to parametric polymorphism.

The _behavior_ of this function is the same for all types, the _output_ is different. That is, for all types, the function body is the same. Maybe there is a more abstract definition of parametric polymorphism you are using, but as I said above, this seems pedantic.


The internal behavior can trivially be made different just by operating on the value:

    fn foo<T>() -> usize {
        let x = std::mem::size_of::<T>();
        if x % 2 == 0 {
            panic!();
        }
    }

    foo::<u8>(); // 1
    foo::<u16>(); // panic
That the body is the same isn't necessarily the issue at hand (though of course it's still a useful property in its own right), what matters is that reasoning about what this function will do requires knowing which specific types it is used with.

> this seems pedantic

The first code example is merely the simplest demonstration, in the wild I would expect lots of `size_of` in generic contexts to result in type-dependent behavior somehow.

I'm not saying this is necessarily a very bad thing, nor do I have strong opinions on the usefulness of strict parametricity (which AFAIK Haskell doesn't have either). But in discussions relevant to parametricity, it's useful to know the ways a given language can subvert it (and Rust will further encourage it to be subverted, once the specialization feature is developed).


> I'm not saying this is necessarily a very bad thing, nor do I have strong opinions on the usefulness of strict parametricity (which AFAIK Haskell doesn't have either). But in discussions relevant to parametricity, it's useful to know the ways a given language can subvert it (and Rust will further encourage it to be subverted, once the specialization feature is developed).

In practice Haskell seems to have pretty strong views on enforcing parametric polymorphism, doesn't it?

Haskell gives you ad-hoc polymorphism via typeclasses and there are also existential types and GADTs etc, if you need those. But once you declare something to abide by parametric polymorphism, you are expected to keep your end of the bargain.

(Yes, you could violate the pact via some unsafePerformIO trickery, but that's considered bad form.)


The whole point of parametric polymorphism (as opposed to eg ad-hoc polymorphism) is that just from reading the type of a function you get a guarantee about the limits of its behaviour.

If you functions routinely violate those limits as a matter of course, those guarantees are useless.

I'm all for abusing notation and terminology a bit when it makes sense in practice, but loosening our definitions too much risks making them useless, too.

In practice in Haskell, I often only need a helper function for eg integers, but when the implementation allows, I will give the function the most parametric-polymorphic type that fits, because that makes the readers job easier:

Just like an invocation of `filter` is easier to read than a for-loop, because `filter` is strictly less powerful than the loop.

(In addition, the more general type serves to give the compiler a hint, so it can yell at me in case I accidentally do an operation on the integer that I didn't mean to.)




Join us for AI Startup School this June 16-17 in San Francisco!

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: