On Sat, Mar 1, 2014 at 8:24 PM, Niko Matsakis <[email protected]> wrote:

> On Fri, Feb 28, 2014 at 10:36:11PM +0100, Gábor Lehel wrote:
> > I don't see the difference here. Why do you think this should be handled
> > differently?
>
> To be honest I hadn't thought about it before. I agree there is no
> fundamental difference, though there may be a practical one. I have to
> mull this over.
>
> I think you're on to something in that there is a connection
> public/private fields, but I'm not quite sure what that ought to
> mean. I dislike the idea that adding a private field changes a whole
> bunch of defaults. (I've at times floating the idea of having a
> `struct`, where things are public-by-default and we auto-derive
> various traits, and a `class`, where things are private-by-default and
> everything is "opt in", but nobody ever likes it but me. It's also
> unclear how to treat enums in such a scheme.)
>

>From this angle I can see more of its appeal... in fact I've been having
vaguely similar ideas. This also ties in to the [nontrivial questions][1]
surrounding how to remove `priv` while making things consistent and
flexible and not requiring too many `pub`s. (In particular, I think it
would be nice if structs and enums, resp. struct-like and tuple-like bodies
for each, could be handled in a consistent fashion.)

[1]: https://github.com/mozilla/rust/issues/8122#issuecomment-31233466

So I was thinking that for both structs and enums, you could have either
all of their contents public or none. (With the potential exception of
individually public struct fields.) It makes no sense to have an enum with
some variants public and others private, nor is there much reason to
support enums with public variants and private fields. The vast majority of
the time, you want either a fully abstract datatype or a fully public one.
(Even with structs, if you have a single private field, you lose the
ability to construct and pattern match, and can only use dot syntax, so
it's closer to a fully-private type than to a fully-public one... but the
ability to control privacy per-field is kind of ingrained and the syntax is
easy enough, so there seems to be no harm in keeping it.)

Anyways, so that's similar to your idea. The hard part is coming up with
syntax. I don't like the name `class`: it has too many connotations, and
very few are ones we want. It *does* happen to have this meaning in C++,
but I think that's sort of accidental, and tends to surprise even C++ users
when they first learn of it (at least in my experience). In other languages
with a struct-class duality, like C# and D, it carries a lot more baggage.

I have a couple of ideas but am not truly satisfied with any of them. In
all cases the default would be that everything is private, and the question
is how to indicate otherwise:

    // this was pnkfelix's idea
    pub struct Point { pub: x: int, y: int }
    pub struct Point(pub: int, int)
    pub enum Option<T> { pub: Some(T), None  }

    // a minor variation putting the `pub` before the opening brace
    pub struct Point pub: { x: int, y: int }
    pub struct Point pub: (int, int)
    pub enum Option<T> pub: { Some(T), None  }

    // use `{ .. }` to indicate the contents are also public:
    pub { .. } struct Point { x: int, y: int }
    // (a bit awkward if the contents don't use braces!)
    pub { .. } struct Point(int, int)
    pub { .. } enum Option<T> { Some(T), None }

    // a variation on the above
    pub { pub } struct Point { x: int, y: int }
    pub { pub } struct Point(int, int)
    pub { pub } enum Option<T> { Some(T), None }



> My first thought regarding variance was that we ought to say that
> any type which is not freeze is invariant with respect to its parameters,
> but that is really quite overly conservative. That means for example
> that something like
>
>     struct MyVec<T> {
>         r: Rc<Vec<T>>
>     }
>
> is not covariant with respect to `T`, which strikes me as quite
> unfortunate! Rc, after all, is not freeze, but it's not freeze because
> of the ref count, not the data it points at.
>

...I still think it would be best to rely on visibility, if we want to
infer anything. (And for variance we probably do, otherwise almost
everything will end up with overconservative defaults.)

In terms of contracts, having public fields is a stronger contract than
stating a variance for a type parameter. You're stating that the type will
have *those exact fields*. Variance is merely a consequence. On the other
hand, if a field is private, the intent is to make no contract at all, so
nothing should be inferred from it.



>
> Some reasons I think it might be reasonable to treat variance differently:
>
> - Nobody but type theorists understands it, at least not at an
>   intuitive level. It has been my experience that many, many very
>   smart people just find it hard to grasp, so declaring variance
>   explicitly is probably bad. Unfortunately, making all type
>   parameters invariant is problematic when working with a type like
>   `Option<&'a T>`.
>
> - It's unclear if the variance of type parameters is likely to evolve
>   over time.
>
> - It may not make much practical difference, since we don't have much
>   subtyping in the language and it's likely to stay that way. I think
>   right now subtyping is *only* induced via subtyping. The variable
>   here is that if we introduced substruct types *and* subtyping rules
>   there, subtyping might be more common.
>

(I think you meant "via lifetimes"? Or maybe "via inference"?)

My Coercible proposal also relies on variance for its equivalent to
substructs, so I think we're going to need it no matter what, if we want
this kind of capability (i.e. coercions/reinterpretations that are legal
only in one direction).


>
>   As an aside, I tried at one point to remove subtyping from the type
>   inference altogether. This was working fine for a while but when I
>   rebased the branch a while back I got lots of errors. I'm still tempted
>   to try again.
>

Interesting, I thought subtyping of lifetimes was important for the system
to work ergonomically? Apparently that's not the case?


>
> - On the other hand, some part of me thinks we ought to just remove
>   the variance inference and instead have a simple variance scheme: no
>   annotation means covariant, otherwise you write `mut T` or `mut 'a`
>   to declare an invariant parameter (intution being: a parameter must
>   be invariant if it used within an interior mutable thing like a
>   `Cell`). That would rather make this question moot.
>

It's unfortunate that this is in tension with covariant being the most
common case in practice, but it really doesn't make sense to choose any
default other than the one which assumes the least, here invariant.
Otherwise you're back to the situation that things like `Cell` are unsafe
by default. And it's not just interior mutability which causes a failure of
covariance: obviously using the type in function argument position will do
the same... as will using it as argument to any invariant type parameter of
another generic type. HashMap is also invariant in its first argument. Etc.


>
> Lots to think about!
>

I'm not even sure if that's the harder part, or writing it all down. But
for sure thinking is more enjoyable. :)


>
>
> Niko
>
_______________________________________________
Rust-dev mailing list
[email protected]
https://mail.mozilla.org/listinfo/rust-dev

Reply via email to