This repository has been archived by the owner. It is now read-only.

Language ergonomic/learnability improvements #17

Open
aturon opened this Issue Jan 31, 2017 · 61 comments

Comments

Projects
None yet
@aturon
Member

aturon commented Jan 31, 2017

Point of contact

@nikomatsakis @aturon @withoutboats

Overview

As part of the effort to lower Rust's learning curve, we are interested in pursuing a number of language improvements that will make Rust both nicer to use and easier to learn. The goal is to enhance productivity without sacrificing Rust's other core values. See the README for more details.

A kind of manifesto for this work was written up on the main blog.

Status

There are a lot of ideas in flight! The lang team has been focused on writing RFCs for the biggest changes; there are lots of opportunities to help either there, or with more narrow RFCs. If you are interested in the ideas listed below, please contact @aturon or others on the team to hook you up with a mentor for RFC writing!

The potential improvements are organized by broad areas of Rust:

Ownership/borrowing

RFCs

Sketches

Traits

RFCs

Sketches

Module and privacy system

Landed

RFCs

Sketches

Error handling

RFCs

Primitive types/core traits

Sketches

Type annotations/verbosity

RFCs

Sketches

FFI

Landed

  • Unions
    • FCP period for stabilization is complete!

Other

RFCs

  • Default struct field values
  • Coroutines, supporting nice async/await syntax
    • RFC postponed, pending some open questions; see the summary and related internals thread
    • There's now a working implementation and async/await notation built on top; we are considering landing that experimentally in the compiler.

More ideas

Ideas that didn't make it

@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon

aturon Feb 1, 2017

Member

Update: added the trait aliases RFC.

Member

aturon commented Feb 1, 2017

Update: added the trait aliases RFC.

@aturon

This comment has been minimized.

Show comment
Hide comment
Member

aturon commented Feb 1, 2017

@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon

aturon Feb 1, 2017

Member

Update: added Delegation of impls

Member

aturon commented Feb 1, 2017

Update: added Delegation of impls

@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon

aturon Feb 1, 2017

Member

Update: added Add else match

Member

aturon commented Feb 1, 2017

Update: added Add else match

@RUSshy

This comment has been minimized.

Show comment
Hide comment
@RUSshy

RUSshy Feb 6, 2017

Please add class sometimes it's just faster and cleaner to use a class

RUSshy commented Feb 6, 2017

Please add class sometimes it's just faster and cleaner to use a class

@Ixrec

This comment has been minimized.

Show comment
Hide comment
@Ixrec

Ixrec Feb 7, 2017

@RUSshy Could you be more specific about what "adding class" means to you? Most languages with "classes" have dozens of distinguishable features associated with their classes, and Rust already has analogues for many of them.

Ixrec commented Feb 7, 2017

@RUSshy Could you be more specific about what "adding class" means to you? Most languages with "classes" have dozens of distinguishable features associated with their classes, and Rust already has analogues for many of them.

@RUSshy

This comment has been minimized.

Show comment
Hide comment
@RUSshy

RUSshy Feb 7, 2017

pub trait Drawable
{
    fn draw(&self);
}

pub trait Updatable
{
    fn update(&self);
}

pub struct Entity
{
    pub id: i32,
    pub x: f64,
    pub y: f64,
    pub z: f64
}

impl Entity
{
    pub fn new(id: i32) -> Self
    {
        Entity{id: id, x: 0.0, y: 0.0, z:0.0}
    }
}

impl Drawable for Entity
{
    fn draw(&self)
    {
        println!("Draw: {}", self.id)
    }
}

impl Updatable for Entity
{
    fn update(&self)
    {
        println!("Update: {}", self.id)
    }
}

to this:

pub class Entity : Drawable, Updatable
{
    pub id: i32;
    pub x: f64;
    pub y: f64;
    pub z: f64;

    pub fn new(id: i32) -> Self
    {
        Entity{id: id, x: 0.0, y: 0.0, z:0.0}
    }

    fn update(&self)
    {
        println!("Update: {}", self.id)
    }

    fn draw(&self)
    {
        println!("Draw: {}", self.id)
    }
}

I love rust, i find it modern and really nice to use, but i think having class to merge struct and impl can help something with readability, because i find myself repeating and write duplicate code

RUSshy commented Feb 7, 2017

pub trait Drawable
{
    fn draw(&self);
}

pub trait Updatable
{
    fn update(&self);
}

pub struct Entity
{
    pub id: i32,
    pub x: f64,
    pub y: f64,
    pub z: f64
}

impl Entity
{
    pub fn new(id: i32) -> Self
    {
        Entity{id: id, x: 0.0, y: 0.0, z:0.0}
    }
}

impl Drawable for Entity
{
    fn draw(&self)
    {
        println!("Draw: {}", self.id)
    }
}

impl Updatable for Entity
{
    fn update(&self)
    {
        println!("Update: {}", self.id)
    }
}

to this:

pub class Entity : Drawable, Updatable
{
    pub id: i32;
    pub x: f64;
    pub y: f64;
    pub z: f64;

    pub fn new(id: i32) -> Self
    {
        Entity{id: id, x: 0.0, y: 0.0, z:0.0}
    }

    fn update(&self)
    {
        println!("Update: {}", self.id)
    }

    fn draw(&self)
    {
        println!("Draw: {}", self.id)
    }
}

I love rust, i find it modern and really nice to use, but i think having class to merge struct and impl can help something with readability, because i find myself repeating and write duplicate code

@bestouff

This comment has been minimized.

Show comment
Hide comment
@bestouff

bestouff Feb 7, 2017

@RUSshy why wouldn't this work:

pub struct Entity : Drawable, Updatable
{
...
}

?

(Otherwise to me, the two most useful items are the ones in "Early stage thoughts", i.e. non-lexical lifetimes and module system improvements)

bestouff commented Feb 7, 2017

@RUSshy why wouldn't this work:

pub struct Entity : Drawable, Updatable
{
...
}

?

(Otherwise to me, the two most useful items are the ones in "Early stage thoughts", i.e. non-lexical lifetimes and module system improvements)

@Nemikolh

This comment has been minimized.

Show comment
Hide comment
@Nemikolh

Nemikolh Feb 7, 2017

@RUSshy It would give wrong impression to beginners in my opinion. One could believe that all the trait being implemented by a type would appear next to the class definition itself. But this is obviously not true (see Sync, Send or Into for instance).

Besides how would you deal with where clauses? What about bounds on type that apply to only specific trait impl but not others?

Would the code really be more readable at this point?

It seems, that you would definitely end up splitting your class into multiple impls as your code evolves.
Anyway, you can actually play with this class idea by writing a macro today. It would not look exactly as you have written it but would still save you a few braces. :)

Nemikolh commented Feb 7, 2017

@RUSshy It would give wrong impression to beginners in my opinion. One could believe that all the trait being implemented by a type would appear next to the class definition itself. But this is obviously not true (see Sync, Send or Into for instance).

Besides how would you deal with where clauses? What about bounds on type that apply to only specific trait impl but not others?

Would the code really be more readable at this point?

It seems, that you would definitely end up splitting your class into multiple impls as your code evolves.
Anyway, you can actually play with this class idea by writing a macro today. It would not look exactly as you have written it but would still save you a few braces. :)

@Ixrec

This comment has been minimized.

Show comment
Hide comment
@Ixrec

Ixrec Feb 7, 2017

i think having class to merge struct and impl can help something with readability, because i find myself repeating and write duplicate code

I don't see any duplicate/repeated code in your example, just three impl blocks merged into the struct definition. I do see the appeal of that sugar unifying "obviously related" blocks of code, but it is just sugar, and it doesn't appear to save very much typing or confusion compared to some of the other proposed sugars such as delegation, and with that particular keyword there's definitely a risk of misleading novices. I agree with Nemikolh that experimenting with a "class macro" would make more sense for now.

Though we should probably move this discussion to https://users.rust-lang.org/ or https://internals.rust-lang.org/ since it's at most a very tiny piece of the puzzle this issue is about.

Ixrec commented Feb 7, 2017

i think having class to merge struct and impl can help something with readability, because i find myself repeating and write duplicate code

I don't see any duplicate/repeated code in your example, just three impl blocks merged into the struct definition. I do see the appeal of that sugar unifying "obviously related" blocks of code, but it is just sugar, and it doesn't appear to save very much typing or confusion compared to some of the other proposed sugars such as delegation, and with that particular keyword there's definitely a risk of misleading novices. I agree with Nemikolh that experimenting with a "class macro" would make more sense for now.

Though we should probably move this discussion to https://users.rust-lang.org/ or https://internals.rust-lang.org/ since it's at most a very tiny piece of the puzzle this issue is about.

@withoutboats

This comment has been minimized.

Show comment
Hide comment
@withoutboats

withoutboats Feb 7, 2017

My impression is that thread is for a high-level coordination of the work toward this roadmap goal. Brainstorming specific language feature ideas should be at either https://internals.rust-lang.org or an issue on the RFC repo https://github.com/rust-lang/rfcs/issues

My impression is that thread is for a high-level coordination of the work toward this roadmap goal. Brainstorming specific language feature ideas should be at either https://internals.rust-lang.org or an issue on the RFC repo https://github.com/rust-lang/rfcs/issues

@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon

aturon Feb 10, 2017

Member

Update: adding reference to this thread on internals which discusses some additional ideas.

Member

aturon commented Feb 10, 2017

Update: adding reference to this thread on internals which discusses some additional ideas.

@aturon

This comment has been minimized.

Show comment
Hide comment
Member

aturon commented Feb 10, 2017

@aturon aturon referenced this issue in rust-lang/rfcs Feb 13, 2017

Closed

Pattern synonyms #1895

@cmr

This comment has been minimized.

Show comment
Hide comment
@cmr

cmr Mar 3, 2017

Member

One idea that would make working with capn proto and similar systems so so much nicer would be something like attributes as exist in C# or Python, where one can use field access syntax but underneath call functions to get/set the field. The current state of the art in Rust is explicit getters and setters, which leads to terrible ergonomics for code interacting with data at the edges of the process boundary, to the point of Java-level verbosity. In the past (pre-1.0) I know there was some discussion and rejection of this, but I'd like to bring it up again.

Member

cmr commented Mar 3, 2017

One idea that would make working with capn proto and similar systems so so much nicer would be something like attributes as exist in C# or Python, where one can use field access syntax but underneath call functions to get/set the field. The current state of the art in Rust is explicit getters and setters, which leads to terrible ergonomics for code interacting with data at the edges of the process boundary, to the point of Java-level verbosity. In the past (pre-1.0) I know there was some discussion and rejection of this, but I'd like to bring it up again.

@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon

aturon Mar 3, 2017

Member

Updated with a link to the new blog post and several in-progress RFCs. If there are pre-RFC internals threads that should be cited, please leave comments and I'll add them!

Member

aturon commented Mar 3, 2017

Updated with a link to the new blog post and several in-progress RFCs. If there are pre-RFC internals threads that should be cited, please leave comments and I'll add them!

@F001

This comment has been minimized.

Show comment
Hide comment
@F001

F001 Mar 4, 2017

The "Delegation of impls" and "Delegation of implementation" both point to rust-lang/rfcs#1806. They are duplicate.

F001 commented Mar 4, 2017

The "Delegation of impls" and "Delegation of implementation" both point to rust-lang/rfcs#1806. They are duplicate.

@crumblingstatue

This comment has been minimized.

Show comment
Hide comment
@crumblingstatue

crumblingstatue Mar 4, 2017

I see default struct field values is listed here.

Are there any learnability improvements that it provides, or are the benefits purely ergonomic?

In the latter case, I find it odd that it would be tracked here. There are lots of ergonomic improvements (e.g. default/keyword args) that are currently postponed, and we couldn't possibly fit them all into this year's roadmap.

I see default struct field values is listed here.

Are there any learnability improvements that it provides, or are the benefits purely ergonomic?

In the latter case, I find it odd that it would be tracked here. There are lots of ergonomic improvements (e.g. default/keyword args) that are currently postponed, and we couldn't possibly fit them all into this year's roadmap.

@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon

aturon Mar 6, 2017

Member

@crumblingstatue I went through the open lang RFCs looking for any that touched on ergonomics, just to have them tracked in a central place.

I'm open to keeping this tracking issue more focused, but I'm not sure what the best "cutoff" line should be.

Member

aturon commented Mar 6, 2017

@crumblingstatue I went through the open lang RFCs looking for any that touched on ergonomics, just to have them tracked in a central place.

I'm open to keeping this tracking issue more focused, but I'm not sure what the best "cutoff" line should be.

@LegNeato

This comment has been minimized.

Show comment
Hide comment
@LegNeato

LegNeato Mar 7, 2017

What's the process for nominating an RFC for this? Sorry if I missed it.

As a Rust n00b I'd like to nominate rust-lang/rfcs#1078 🍻

LegNeato commented Mar 7, 2017

What's the process for nominating an RFC for this? Sorry if I missed it.

As a Rust n00b I'd like to nominate rust-lang/rfcs#1078 🍻

@withoutboats

This comment has been minimized.

Show comment
Hide comment
@withoutboats

withoutboats Mar 7, 2017

That issue has an RFC which will probably be merged soon :-) rust-lang/rfcs#1869

That issue has an RFC which will probably be merged soon :-) rust-lang/rfcs#1869

@hgrecco

This comment has been minimized.

Show comment
Hide comment
@hgrecco

hgrecco Mar 9, 2017

I find this example from the book quite annoying:

enum Message {
    Quit,
    ChangeColor(i32, i32, i32),
    Move { x: i32, y: i32 },
    Write(String),
}

fn process_message(msg: Message) {
    match msg {
        Message::Quit => quit(),
        Message::ChangeColor(r, g, b) => change_color(r, g, b),
        Message::Move { x: x, y: y } => move_cursor(x, y),
        Message::Write(s) => println!("{}", s),
    };
}

If you are matching an Enum variant, cannot Message:: (in this case) be implicit?

fn process_message(msg: Message) {
    match msg {
        Quit => quit(),
        ChangeColor(r, g, b) => change_color(r, g, b),
        Move { x: x, y: y } => move_cursor(x, y),
        Write(s) => println!("{}", s),
    };
}

I have seen ways to mitigate this (such as use Message::*) but I think some implicitness here will be nicer.

hgrecco commented Mar 9, 2017

I find this example from the book quite annoying:

enum Message {
    Quit,
    ChangeColor(i32, i32, i32),
    Move { x: i32, y: i32 },
    Write(String),
}

fn process_message(msg: Message) {
    match msg {
        Message::Quit => quit(),
        Message::ChangeColor(r, g, b) => change_color(r, g, b),
        Message::Move { x: x, y: y } => move_cursor(x, y),
        Message::Write(s) => println!("{}", s),
    };
}

If you are matching an Enum variant, cannot Message:: (in this case) be implicit?

fn process_message(msg: Message) {
    match msg {
        Quit => quit(),
        ChangeColor(r, g, b) => change_color(r, g, b),
        Move { x: x, y: y } => move_cursor(x, y),
        Write(s) => println!("{}", s),
    };
}

I have seen ways to mitigate this (such as use Message::*) but I think some implicitness here will be nicer.

@Manishearth

This comment has been minimized.

Show comment
Hide comment
@Manishearth

Manishearth Mar 9, 2017

Member

Please file bugs on the Rust or rfcs repo, this is for tracking efforts to improve readability and not discussing the specifics themselves.

(In that specific case, it used to be the opposite, actually, but then everyone namespaced enums as QuitMessage, etc.)

Member

Manishearth commented Mar 9, 2017

Please file bugs on the Rust or rfcs repo, this is for tracking efforts to improve readability and not discussing the specifics themselves.

(In that specific case, it used to be the opposite, actually, but then everyone namespaced enums as QuitMessage, etc.)

@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon

aturon Mar 9, 2017

Member

@Manishearth @hgrecco to clarify, I'd actually recommend opening threads on https://internals.rust-lang.org/ with ideas, and then posting a link here. I am using the comment thread/headers to track ideas floating around.

Member

aturon commented Mar 9, 2017

@Manishearth @hgrecco to clarify, I'd actually recommend opening threads on https://internals.rust-lang.org/ with ideas, and then posting a link here. I am using the comment thread/headers to track ideas floating around.

@Manishearth

This comment has been minimized.

Show comment
Hide comment
@Manishearth

Manishearth Mar 9, 2017

Member

Yeah, that works too.

Member

Manishearth commented Mar 9, 2017

Yeah, that works too.

@hgrecco

This comment has been minimized.

Show comment
Hide comment
@hgrecco

hgrecco Mar 9, 2017

I apologize for the noise. Here is the post in rust internals: https://internals.rust-lang.org/t/elliding-type-in-matching-an-enum/4935

hgrecco commented Mar 9, 2017

I apologize for the noise. Here is the post in rust internals: https://internals.rust-lang.org/t/elliding-type-in-matching-an-enum/4935

@suhr

This comment has been minimized.

Show comment
Hide comment
@suhr

suhr Mar 10, 2017

There's one old but still not fixed ergonomics problem: while you can do let (x, y) = ... you can't do (x, y) = ....

The open rust issue (from 2014!): rust-lang/rfcs#372

suhr commented Mar 10, 2017

There's one old but still not fixed ergonomics problem: while you can do let (x, y) = ... you can't do (x, y) = ....

The open rust issue (from 2014!): rust-lang/rfcs#372

@Keats

This comment has been minimized.

Show comment
Hide comment
@Keats

Keats Apr 8, 2017

There's this pre-RFC for named/default args: https://internals.rust-lang.org/t/pre-rfc-named-arguments/3831 which has stagnated a bit right now

Keats commented Apr 8, 2017

There's this pre-RFC for named/default args: https://internals.rust-lang.org/t/pre-rfc-named-arguments/3831 which has stagnated a bit right now

@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon

aturon Apr 13, 2017

Member

I've extensively updated the issue with a lot more detail on the lang team's plans and various ideas that are currently in flight. I've tried to tag items with a lang team member where there's a clear person trying to push forward an idea. I've also called out opportunities for mentored RFCs (just search for "mentor").

Let me know what's missing!

Member

aturon commented Apr 13, 2017

I've extensively updated the issue with a lot more detail on the lang team's plans and various ideas that are currently in flight. I've tried to tag items with a lang team member where there's a clear person trying to push forward an idea. I've also called out opportunities for mentored RFCs (just search for "mentor").

Let me know what's missing!

@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon

aturon Apr 13, 2017

Member

Added coroutines; supporting async/await notation in some way could be a very important ergonomic/learnability improvement to the extent that we're leaning on futures-based abstractions for async IO.

Member

aturon commented Apr 13, 2017

Added coroutines; supporting async/await notation in some way could be a very important ergonomic/learnability improvement to the extent that we're leaning on futures-based abstractions for async IO.

@mdinger

This comment has been minimized.

Show comment
Hide comment
@mdinger

mdinger Apr 14, 2017

The Eliding type name in match arms topic has an RFC and a separate unrelated issue.

mdinger commented Apr 14, 2017

The Eliding type name in match arms topic has an RFC and a separate unrelated issue.

@dpc

This comment has been minimized.

Show comment
Hide comment
@dpc

dpc Apr 14, 2017

Would addressing rust-lang/rust#40628 be considered to be added somewhere here?

dpc commented Apr 14, 2017

Would addressing rust-lang/rust#40628 be considered to be added somewhere here?

@MaikKlein

This comment has been minimized.

Show comment
Hide comment
@MaikKlein

MaikKlein Apr 14, 2017

What about partial type inference? I know that there was an rfc for this but I can't find it anymore.
playground

fn test<A, B>(b: B){}
fn main() {
    let i: u32 = 42;
    test::<i32, u32>(i); // u32 could be inferred
    // test::<i32>(i);
}

What about partial type inference? I know that there was an rfc for this but I can't find it anymore.
playground

fn test<A, B>(b: B){}
fn main() {
    let i: u32 = 42;
    test::<i32, u32>(i); // u32 could be inferred
    // test::<i32>(i);
}
@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon

aturon Apr 14, 2017

Member

@dpc Oy, I hadn't seen that issue, but it's a doozy. Definitely something worth thinking about, though it's not obvious how to best address it.

@MaikKlein That definitely falls under the umbrella of ergonomics, but it doesn't seem particularly high impact. It does remind me, though, that we still need to sort out our story around defaulted type parameters and fallback etc.

Member

aturon commented Apr 14, 2017

@dpc Oy, I hadn't seen that issue, but it's a doozy. Definitely something worth thinking about, though it's not obvious how to best address it.

@MaikKlein That definitely falls under the umbrella of ergonomics, but it doesn't seem particularly high impact. It does remind me, though, that we still need to sort out our story around defaulted type parameters and fallback etc.

@suhr

This comment has been minimized.

Show comment
Hide comment
@suhr

suhr Apr 15, 2017

@MaikKlein test::<i32, _>(i) seems to work just fine. While test::<i32>(i) looks like test has only one parameter.

suhr commented Apr 15, 2017

@MaikKlein test::<i32, _>(i) seems to work just fine. While test::<i32>(i) looks like test has only one parameter.

@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon

aturon Apr 15, 2017

Member

I wanted to copy in a conversation from reddit:

... many on the libs + lang teams think that the distinction (between PartialOrd and Ord) is more trouble than it's worth, i.e. that the bugs being prevented here were pretty hypothetical, but it introduces friction for everyone, especially newcomers.

Bugs are hypothetical until they are real.

The problem with this perspective is that it doesn't acknowledge the tradeoffs involved. At the extreme, the only way to rule out all bugs is to work in a language where you can prove the correctness of programs (like Coq). But it's very impractical, and only worthwhile for the most critical software.

Rust strives, amongst other things, to be a practical programming language, and that requires us to avoid extremes and instead make judicious tradeoffs. In the design of the standard library, there were many such tradeoffs, and we learned over time that additional static distinctions are worthwhile only for cases where bugs are relatively common and otherwise hard to detect (or are extremely destructive). In a lot of cases where we could have used more type machinery to rule out potential bugs, those bugs would've been caught with even the most trivial testing.

For example, we could have used a typestate encoding to statically ensure that custom socket setup APIs are called only in valid states, but if you have test coverage you will catch this very quickly.

The motivation for PartialOrd and friends was to prevent people from doing things like using floats as a BTreeMap key. But that's an extremely obscure thing to try to do that would lead to much more obvious bugs in practice. The price for the distinction, however, is that every person learning Rust has to learn this unusual distinction, and people defining types often have to say #[derive(PartialOrd, Ord, PartialEq, Eq)] rather than #[derive(Ord, Eq)]. It's a paper cut, but paper cuts add up, and I really don't think this one is worth it.

Member

aturon commented Apr 15, 2017

I wanted to copy in a conversation from reddit:

... many on the libs + lang teams think that the distinction (between PartialOrd and Ord) is more trouble than it's worth, i.e. that the bugs being prevented here were pretty hypothetical, but it introduces friction for everyone, especially newcomers.

Bugs are hypothetical until they are real.

The problem with this perspective is that it doesn't acknowledge the tradeoffs involved. At the extreme, the only way to rule out all bugs is to work in a language where you can prove the correctness of programs (like Coq). But it's very impractical, and only worthwhile for the most critical software.

Rust strives, amongst other things, to be a practical programming language, and that requires us to avoid extremes and instead make judicious tradeoffs. In the design of the standard library, there were many such tradeoffs, and we learned over time that additional static distinctions are worthwhile only for cases where bugs are relatively common and otherwise hard to detect (or are extremely destructive). In a lot of cases where we could have used more type machinery to rule out potential bugs, those bugs would've been caught with even the most trivial testing.

For example, we could have used a typestate encoding to statically ensure that custom socket setup APIs are called only in valid states, but if you have test coverage you will catch this very quickly.

The motivation for PartialOrd and friends was to prevent people from doing things like using floats as a BTreeMap key. But that's an extremely obscure thing to try to do that would lead to much more obvious bugs in practice. The price for the distinction, however, is that every person learning Rust has to learn this unusual distinction, and people defining types often have to say #[derive(PartialOrd, Ord, PartialEq, Eq)] rather than #[derive(Ord, Eq)]. It's a paper cut, but paper cuts add up, and I really don't think this one is worth it.

@burdges

This comment has been minimized.

Show comment
Hide comment
@burdges

burdges Apr 15, 2017

Couldn't #[derive(Ord)], etc. imply #[derive(PartialOrd)], etc. unless PartialOrd, etc. already exists for this type?

burdges commented Apr 15, 2017

Couldn't #[derive(Ord)], etc. imply #[derive(PartialOrd)], etc. unless PartialOrd, etc. already exists for this type?

@frankmcsherry

This comment has been minimized.

Show comment
Hide comment
@frankmcsherry

frankmcsherry Apr 15, 2017

A quick comment on PartialOrd. Whether this was its intended use or not, a partial order is actually a useful pre-existing thing. If you kill it off, some useful code is certainly going to break (all of timely and differential dataflow will have to pivot to a new custom trait). Yes, I understand "deprecate" doesn't mean "break", but it does mean "break and make others fix".

I suspect the bigger problem is "how can we have hierarchies of traits without forcing the user to acknowledge the complexity all the way down?". This problem doesn't seem to be specific to Ord and PartialOrd; you would have it with other hierarchies (e.g. Ring : Add+Sub+Mul) that are perhaps just less popular. What seems to have happened with the arithmetic traits (Add, Sub, Neg, Mul, etc) is that there is no attempt at hierarchy, and each is just "how you bind '+', '-', '*', etc). You could do that with Ord (it is just how you bind < etc; no guarantees).

Or, maybe I mis-understand and the real problem is "how few characters can a Rust beginner type before have a struct they can sort?". That a fine question to solve, but .. it would be a bit annoying to have to rewrite a lot of code because the solution was to damage the existing trait hierarchy.

Either way, it seems like there isn't much clarity on the Ord/PartialOrd distinction at the moment, in that there are some implicit laws that the standard library assumes about their implementations (that if you impl Ord, your PartialOrd impl is equivalent). It would be great to clarify things, and if that means dropping PartialOrd fair enough. I'd personally prefer that derive(Ord) get you an Ord impl (perhaps deriving a PartialOrd impl too, preventing the derivation of conflicting implementations), and PartialOrd lives on as how you hook < and friends, but without bothering people who don't care about the distinction.

frankmcsherry commented Apr 15, 2017

A quick comment on PartialOrd. Whether this was its intended use or not, a partial order is actually a useful pre-existing thing. If you kill it off, some useful code is certainly going to break (all of timely and differential dataflow will have to pivot to a new custom trait). Yes, I understand "deprecate" doesn't mean "break", but it does mean "break and make others fix".

I suspect the bigger problem is "how can we have hierarchies of traits without forcing the user to acknowledge the complexity all the way down?". This problem doesn't seem to be specific to Ord and PartialOrd; you would have it with other hierarchies (e.g. Ring : Add+Sub+Mul) that are perhaps just less popular. What seems to have happened with the arithmetic traits (Add, Sub, Neg, Mul, etc) is that there is no attempt at hierarchy, and each is just "how you bind '+', '-', '*', etc). You could do that with Ord (it is just how you bind < etc; no guarantees).

Or, maybe I mis-understand and the real problem is "how few characters can a Rust beginner type before have a struct they can sort?". That a fine question to solve, but .. it would be a bit annoying to have to rewrite a lot of code because the solution was to damage the existing trait hierarchy.

Either way, it seems like there isn't much clarity on the Ord/PartialOrd distinction at the moment, in that there are some implicit laws that the standard library assumes about their implementations (that if you impl Ord, your PartialOrd impl is equivalent). It would be great to clarify things, and if that means dropping PartialOrd fair enough. I'd personally prefer that derive(Ord) get you an Ord impl (perhaps deriving a PartialOrd impl too, preventing the derivation of conflicting implementations), and PartialOrd lives on as how you hook < and friends, but without bothering people who don't care about the distinction.

@Manishearth

This comment has been minimized.

Show comment
Hide comment
@Manishearth

Manishearth Apr 15, 2017

Member

how can we have hierarchies of traits without forcing the user to acknowledge the complexity all the way down?

This brushes on a very related issue with trait usability. We have a lot of "drill down" traits, like Eq, Serialize, Hash, etc. Most implementations are a #[derive()] away (with stable derive we may see an explosion of more such traits like this). Some are special. It's often a problem that a crate you use doesn't derive a particular trait (especially for non-std traits. this problem is assuaged by the automatic features RFC, but not fixed) and you need to go add it. Eq and Ord are the worst offenders here, because folks seem to often implement the partial ones but not the total ones.

I'm not really proposing a proper solution here, just mentioning that these traits have a class of problems associated with them. The class of problems comes from the fact that these traits are usually structurally-derived but sometimes need manual impls, and currently the structural-derive needs explicit opt in (which makes sense, really).

Throwing a half solution into the mix: in the past we have discussed things like deferred derive where derive(Trait) defers codegen until the crate that actually needs it is compiled. Similar mechanisms could be used to drill down to other objects, for example you could do something like #[derive(Hash)] pub struct Foo {x: u8, #[inline_derive(Hash)] y: somecrate::SomeType}, where SomeType doesn't impl Hash, but the compiler will try to structural derive Hash as deep as it needs to, if it can, and inline that code. Serde already has a (hacky) solution for this where you can specify a custom serializer function in an attribute which will be used instead. That feature has other, less hacky use cases, but when used to solve this problem you still have to do some annoying manual writing of derives.

This "solution" if used too much would have codesize bloat problems, and there's also the whole interaction with safety (you don't want someone to force-derive on an unsafe abstraction), but it's something that can be built upon.

Member

Manishearth commented Apr 15, 2017

how can we have hierarchies of traits without forcing the user to acknowledge the complexity all the way down?

This brushes on a very related issue with trait usability. We have a lot of "drill down" traits, like Eq, Serialize, Hash, etc. Most implementations are a #[derive()] away (with stable derive we may see an explosion of more such traits like this). Some are special. It's often a problem that a crate you use doesn't derive a particular trait (especially for non-std traits. this problem is assuaged by the automatic features RFC, but not fixed) and you need to go add it. Eq and Ord are the worst offenders here, because folks seem to often implement the partial ones but not the total ones.

I'm not really proposing a proper solution here, just mentioning that these traits have a class of problems associated with them. The class of problems comes from the fact that these traits are usually structurally-derived but sometimes need manual impls, and currently the structural-derive needs explicit opt in (which makes sense, really).

Throwing a half solution into the mix: in the past we have discussed things like deferred derive where derive(Trait) defers codegen until the crate that actually needs it is compiled. Similar mechanisms could be used to drill down to other objects, for example you could do something like #[derive(Hash)] pub struct Foo {x: u8, #[inline_derive(Hash)] y: somecrate::SomeType}, where SomeType doesn't impl Hash, but the compiler will try to structural derive Hash as deep as it needs to, if it can, and inline that code. Serde already has a (hacky) solution for this where you can specify a custom serializer function in an attribute which will be used instead. That feature has other, less hacky use cases, but when used to solve this problem you still have to do some annoying manual writing of derives.

This "solution" if used too much would have codesize bloat problems, and there's also the whole interaction with safety (you don't want someone to force-derive on an unsafe abstraction), but it's something that can be built upon.

@djc

This comment has been minimized.

Show comment
Hide comment
@djc

djc Apr 16, 2017

It seems like @Manishearth's comment is really concerned with what I call deriving-at-a-distance. However, if derive(X) today is really syntax sugar for writing out an impl X for T, should Rust not provide you with the option to derive X for T for use in the local crate even if T's origin crate doesn't derive X? I was thinking about this recently in the context of Askama templates and wanting to implement a template for some context in another crate (mostly to allow plugin templates as dylibs).

djc commented Apr 16, 2017

It seems like @Manishearth's comment is really concerned with what I call deriving-at-a-distance. However, if derive(X) today is really syntax sugar for writing out an impl X for T, should Rust not provide you with the option to derive X for T for use in the local crate even if T's origin crate doesn't derive X? I was thinking about this recently in the context of Askama templates and wanting to implement a template for some context in another crate (mostly to allow plugin templates as dylibs).

@Manishearth

This comment has been minimized.

Show comment
Hide comment
@Manishearth

Manishearth Apr 16, 2017

Member

should Rust not provide you with the option to derive X for T for use in the local crate even if T's origin crate doesn't derive X?

Well, you have coherence problems there.

Unless you're the one defining the deriving stuff. Crates like serde/heapsize may have to do manual tinkering to implement these traits since they themselves can't apply the deriving syntax to things from std.

My solution to the drill down issue was to ignore the part of generating impls at a distance, and just make the codegen accessible. Impls come with a whole host of other problems (coherence) 😄

Anyway, this is kind of off topic I guess.

Member

Manishearth commented Apr 16, 2017

should Rust not provide you with the option to derive X for T for use in the local crate even if T's origin crate doesn't derive X?

Well, you have coherence problems there.

Unless you're the one defining the deriving stuff. Crates like serde/heapsize may have to do manual tinkering to implement these traits since they themselves can't apply the deriving syntax to things from std.

My solution to the drill down issue was to ignore the part of generating impls at a distance, and just make the codegen accessible. Impls come with a whole host of other problems (coherence) 😄

Anyway, this is kind of off topic I guess.

@eddyb

This comment has been minimized.

Show comment
Hide comment
@eddyb

eddyb Apr 16, 2017

Member

@Manishearth Crate introspection is possible after loading their metadata, so perhaps a feature along the lines of #[derive(Serialize, Deserialize)] use std::ops::Range; could be added.

Member

eddyb commented Apr 16, 2017

@Manishearth Crate introspection is possible after loading their metadata, so perhaps a feature along the lines of #[derive(Serialize, Deserialize)] use std::ops::Range; could be added.

@Manishearth

This comment has been minimized.

Show comment
Hide comment
@Manishearth

Manishearth Apr 16, 2017

Member

Yep. We have discussed this before, I think 😄

I understand that it's feasible, it's just designing the exact API (and figuring out if we actually need it) that needs doing.

Member

Manishearth commented Apr 16, 2017

Yep. We have discussed this before, I think 😄

I understand that it's feasible, it's just designing the exact API (and figuring out if we actually need it) that needs doing.

@burdges

This comment has been minimized.

Show comment
Hide comment
@burdges

burdges Apr 16, 2017

Is there any problem with using specialization to reduce the boilerplate around PartialEq and PartialOrd?

impl<T> PartialEq<Self> for T where T: Ord {
    default fn eq(&self, other: &Self) -> bool { Ord::cmp(self, other) == Ordering::Equal }
}
impl<T> PartialOrd<Self> for T where T: Ord {
    default fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Ok( Ord::cmp(self,other) ) }
}

I suppose impl<T> Eq for T where T: Ord { } might work too, but maybe you want Eq to serve as a reminder.

burdges commented Apr 16, 2017

Is there any problem with using specialization to reduce the boilerplate around PartialEq and PartialOrd?

impl<T> PartialEq<Self> for T where T: Ord {
    default fn eq(&self, other: &Self) -> bool { Ord::cmp(self, other) == Ordering::Equal }
}
impl<T> PartialOrd<Self> for T where T: Ord {
    default fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Ok( Ord::cmp(self,other) ) }
}

I suppose impl<T> Eq for T where T: Ord { } might work too, but maybe you want Eq to serve as a reminder.

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Apr 19, 2017

@eddyb

Crate introspection is possible after loading their metadata, so perhaps a feature along the lines of #[derive(Serialize, Deserialize)] use std::ops::Range; could be added.

Can you clarify what you envision this syntax as doing? It seems to me that the interesting (and hard) problem here is deciding what to do about coherence. Ah, would this be something we would do in the crate that defines Serialize?

@eddyb

Crate introspection is possible after loading their metadata, so perhaps a feature along the lines of #[derive(Serialize, Deserialize)] use std::ops::Range; could be added.

Can you clarify what you envision this syntax as doing? It seems to me that the interesting (and hard) problem here is deciding what to do about coherence. Ah, would this be something we would do in the crate that defines Serialize?

@eddyb

This comment has been minimized.

Show comment
Hide comment
@eddyb

eddyb Apr 19, 2017

Member

@nikomatsakis Yupp, giving the derives an approximation of the original AST, from crate metadata.

Member

eddyb commented Apr 19, 2017

@nikomatsakis Yupp, giving the derives an approximation of the original AST, from crate metadata.

@shepmaster

This comment has been minimized.

Show comment
Hide comment
@shepmaster

shepmaster Apr 21, 2017

Member

Is there a more detailed location to add thoughts about "Allow owned values where references are expected" ?

Member

shepmaster commented Apr 21, 2017

Is there a more detailed location to add thoughts about "Allow owned values where references are expected" ?

@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon

aturon Apr 24, 2017

Member

@shepmaster There's not currently, but you're welcome to make an internals thread on the topic (and I'll link it here)!

FWIW, the strawman proposal is to automatically coerce via AsRef or AsMut, which will have the effect of allowing owned where borrowed is expected. This automatic coercion is not like full-blown implicit coercions, because the types involved (going from a reference to a reference) strongly limit what you can do, much like with Deref and deref coercions.

We already use this pattern in std, but it'd be nice to have it apply uniformly and not have to muck up signatures with it.

Member

aturon commented Apr 24, 2017

@shepmaster There's not currently, but you're welcome to make an internals thread on the topic (and I'll link it here)!

FWIW, the strawman proposal is to automatically coerce via AsRef or AsMut, which will have the effect of allowing owned where borrowed is expected. This automatic coercion is not like full-blown implicit coercions, because the types involved (going from a reference to a reference) strongly limit what you can do, much like with Deref and deref coercions.

We already use this pattern in std, but it'd be nice to have it apply uniformly and not have to muck up signatures with it.

@aturon

This comment has been minimized.

Show comment
Hide comment
Member

aturon commented Apr 29, 2017

@shepmaster

This comment has been minimized.

Show comment
Hide comment
@shepmaster

shepmaster Apr 29, 2017

Member

you're welcome to make an internals thread on the topic

@aturon I've created a thread as requested. We'll see where it goes ;-)

Member

shepmaster commented Apr 29, 2017

you're welcome to make an internals thread on the topic

@aturon I've created a thread as requested. We'll see where it goes ;-)

@schuster schuster referenced this issue in rust-lang/rfcs May 29, 2017

Closed

Const/static type annotation elision #2010

@aturon aturon self-assigned this Jun 7, 2017

@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon

aturon Jun 7, 2017

Member

Updated with the latest status info.

Member

aturon commented Jun 7, 2017

Updated with the latest status info.

@le-jzr

This comment has been minimized.

Show comment
Hide comment
@le-jzr

le-jzr Jun 9, 2017

Any news regarding PartialEq/PartialOrd? I find @burdges's solution elegant and nobody suggested any problems.

It would also solve the weirdness where current bound Ord: PartialOrd<Self> means "for a type to be Ord, it must be PartialOrd first", whereas the true mathematical relation is that Ord is automatically PartialOrd<Self>, Eq and PartialEq<Self> as well, by virtue of Ord subsuming all those properties, so encoding the relationship in a trait bound actually makes no sense.

le-jzr commented Jun 9, 2017

Any news regarding PartialEq/PartialOrd? I find @burdges's solution elegant and nobody suggested any problems.

It would also solve the weirdness where current bound Ord: PartialOrd<Self> means "for a type to be Ord, it must be PartialOrd first", whereas the true mathematical relation is that Ord is automatically PartialOrd<Self>, Eq and PartialEq<Self> as well, by virtue of Ord subsuming all those properties, so encoding the relationship in a trait bound actually makes no sense.

@sfackler

This comment has been minimized.

Show comment
Hide comment
@sfackler

sfackler Jun 9, 2017

Member

@le-jzr I'd love to have automatic implementations like that - it'd also be possible to implement Read automatically for BufRead types. We may need to wait until after specialization stabilizes to be able to add them, though, since I believe you need to enable the feature to specialize an implementation.

Member

sfackler commented Jun 9, 2017

@le-jzr I'd love to have automatic implementations like that - it'd also be possible to implement Read automatically for BufRead types. We may need to wait until after specialization stabilizes to be able to add them, though, since I believe you need to enable the feature to specialize an implementation.

@glaebhoerl

This comment has been minimized.

Show comment
Hide comment
@glaebhoerl

glaebhoerl Jun 12, 2017

I think this RFC ("Allow comparisons between integers of different types") is also relevant here. Much like default binding modes for match, it takes a case where the compiler today issues errors and forces people to manually write boilerplate and makes it just do the obviously right thing instead. (Except in this case, the manual boilerplate is also highly error-prone.)

I think this RFC ("Allow comparisons between integers of different types") is also relevant here. Much like default binding modes for match, it takes a case where the compiler today issues errors and forces people to manually write boilerplate and makes it just do the obviously right thing instead. (Except in this case, the manual boilerplate is also highly error-prone.)

@Migi Migi referenced this issue in rust-lang/rust Jun 17, 2017

Closed

Allow T op= &T for built-in numeric types T #41336

@suhr

This comment has been minimized.

Show comment
Hide comment
@suhr

suhr Jun 23, 2017

use inside trait definitions.

Local use statements are an extremely cool rust feature. But unfortunately, there're some places where you can't use them. One of such places are trait definitions.

Long story short, I want to be able to write something like this:

trait Foo {
    use std::iter::IntoIterator;
    // ...
}

suhr commented Jun 23, 2017

use inside trait definitions.

Local use statements are an extremely cool rust feature. But unfortunately, there're some places where you can't use them. One of such places are trait definitions.

Long story short, I want to be able to write something like this:

trait Foo {
    use std::iter::IntoIterator;
    // ...
}
@crumblingstatue

This comment has been minimized.

Show comment
Hide comment
@crumblingstatue

crumblingstatue Jun 23, 2017

@suhr: That's rust-lang/rfcs#1976, and it's about to get postponed.

@suhr: That's rust-lang/rfcs#1976, and it's about to get postponed.

@burdges

This comment has been minimized.

Show comment
Hide comment
@burdges

burdges Jun 23, 2017

It turned out trickier than folks thought. I think you can mostly work around it using submodules judiciously.

mod UseNo1 {
use std::iter::IntoIterator;
pub(super) trait Foo { ... }
} // UseNo1
pub use UseNo1::*;

burdges commented Jun 23, 2017

It turned out trickier than folks thought. I think you can mostly work around it using submodules judiciously.

mod UseNo1 {
use std::iter::IntoIterator;
pub(super) trait Foo { ... }
} // UseNo1
pub use UseNo1::*;
@suhr

This comment has been minimized.

Show comment
Hide comment
@suhr

suhr Jun 23, 2017

it's about to get postponed

Well, that's unfortunate.

I think you can mostly work around it using submodules judiciously.

It's an ugly and awkward kludge that is not worth it.

suhr commented Jun 23, 2017

it's about to get postponed

Well, that's unfortunate.

I think you can mostly work around it using submodules judiciously.

It's an ugly and awkward kludge that is not worth it.

@dobkeratops

This comment has been minimized.

Show comment
Hide comment
@dobkeratops

dobkeratops Jul 25, 2017

I wonder if this would get a different reaction in the context of this thread.
RFC: rust-lang/rfcs#2063
link to text:
https://github.com/dobkeratops/rfcs/blob/infer-function-signatures-from-trait-declaration-into-'impl's/text/infer%20function%20signatures%20from%20trait%20declaration%20into%20impls.md

alternating between languages, I continue to find the need to micromanage traits to be irritating (i.e. the single function trait case), but strangely in haskell the typeclasses don't feel as bad. It's because the there, defining and referring to the 'type class' saves you writing details out again. Initially I found it odd, but their function signatures being split are actually clearer.

Hence this PR.

People are complaining 'you read it more than you write it' but it's the repetition that is annoying: if I'm specifying the types , I would rather it figures out the trait from them. If I've specified the trait, it makes sense to use that to infer the types. Users are going to read the trait declaration itself more surely, which has all the types, and you need to refer back to that to implement anyway, surely. Aren't you just taking up more screenspace in a useless way displaying it again. The trait defs are very easy to grep for ( i have that all working nicely in emacs), and there's rustdoc too.

Something else to mention is this mental flip of arguments in the single-function-trait case (e.g. used for overloading)..

I wish you could write the impl the other way round so that it's impl Type:Trait<..> , so the types appear in the same order as the function definition
e.g.

    // "I want to implement LHS.add(RHS)->Output"
    impl LHS:Add<RHS> { type Output=..;    // trait: "LHS,add, RHS, Output.."
     //  |          |           |
        fn add(&self,rhs){... }       //function: "add LHS,RHS,Output"
    }

wouldn't that be so much bettter? the mental flip between the intent "LHS,add,RHS" and expressing the trait makes the trait so much more annoying.

Again the 'multi-parameter-typeclass' case in haskell is more pleasant, possibly because you get to choose the order, so in the case of 'a single function class' you can write the class-types in the same order as the function types. (there's extra symmetry from the fact they have no 'self', but with better ordering I don't think special-Self would be a pain)

Combined with eliding the signature types, that might be enough for me to achieve peace with this system.

dobkeratops commented Jul 25, 2017

I wonder if this would get a different reaction in the context of this thread.
RFC: rust-lang/rfcs#2063
link to text:
https://github.com/dobkeratops/rfcs/blob/infer-function-signatures-from-trait-declaration-into-'impl's/text/infer%20function%20signatures%20from%20trait%20declaration%20into%20impls.md

alternating between languages, I continue to find the need to micromanage traits to be irritating (i.e. the single function trait case), but strangely in haskell the typeclasses don't feel as bad. It's because the there, defining and referring to the 'type class' saves you writing details out again. Initially I found it odd, but their function signatures being split are actually clearer.

Hence this PR.

People are complaining 'you read it more than you write it' but it's the repetition that is annoying: if I'm specifying the types , I would rather it figures out the trait from them. If I've specified the trait, it makes sense to use that to infer the types. Users are going to read the trait declaration itself more surely, which has all the types, and you need to refer back to that to implement anyway, surely. Aren't you just taking up more screenspace in a useless way displaying it again. The trait defs are very easy to grep for ( i have that all working nicely in emacs), and there's rustdoc too.

Something else to mention is this mental flip of arguments in the single-function-trait case (e.g. used for overloading)..

I wish you could write the impl the other way round so that it's impl Type:Trait<..> , so the types appear in the same order as the function definition
e.g.

    // "I want to implement LHS.add(RHS)->Output"
    impl LHS:Add<RHS> { type Output=..;    // trait: "LHS,add, RHS, Output.."
     //  |          |           |
        fn add(&self,rhs){... }       //function: "add LHS,RHS,Output"
    }

wouldn't that be so much bettter? the mental flip between the intent "LHS,add,RHS" and expressing the trait makes the trait so much more annoying.

Again the 'multi-parameter-typeclass' case in haskell is more pleasant, possibly because you get to choose the order, so in the case of 'a single function class' you can write the class-types in the same order as the function types. (there's extra symmetry from the fact they have no 'self', but with better ordering I don't think special-Self would be a pain)

Combined with eliding the signature types, that might be enough for me to achieve peace with this system.

@burdges

This comment has been minimized.

Show comment
Hide comment
@burdges

burdges Jul 25, 2017

I believe mutability, references, lifetimes, etc. change that calculation somewhat from Haskell, so maybe wait until folks become more comfortable with lifetime elision for impls rust-lang/rust#15872 ?

At least one can then deduce if type elision ever confuses the reader familiar with lifetime elision. If so, type elision could be restricted to cases where lifetime elision works. And maybe it should be restricted to not doing muts anyways.

burdges commented Jul 25, 2017

I believe mutability, references, lifetimes, etc. change that calculation somewhat from Haskell, so maybe wait until folks become more comfortable with lifetime elision for impls rust-lang/rust#15872 ?

At least one can then deduce if type elision ever confuses the reader familiar with lifetime elision. If so, type elision could be restricted to cases where lifetime elision works. And maybe it should be restricted to not doing muts anyways.

@dobkeratops

This comment has been minimized.

Show comment
Hide comment
@dobkeratops

dobkeratops Jul 26, 2017

most of the time operators are just reference inputs, and the lifetime elision kicks in;
Thinking in C++, I'm used to 2 main cases which are both quite simple:

  • 'accessors' ('self assumption' which Rust successfully allows elision for - this is great) ,
  • and 'no returned references'/'independent result' (harder to give this a catchy name, but basically 'there are no returned references to worry about' - any pointers are fully owned and managed by the result; I appreciate the need to mark up the difference, however I still suspect there might be an easier way to do this: often there's still a gap between my intent ("no escaped references", and the nature of the markup'(ok, show it which references in the inputs correspond to each other, specifically')

So basically the times you actually need to write lifetimes, you're doing something rarer (IMO).

Eliding information in the obvious cases will give you more 'cognitive budget' to spend on the unusual.

Also the information is still there and will surface if you hit compile.

I argue people reading code are more likely to be reading the trait, which does have the full types.

also I argue haskell's split is actually helping readability, regardless of how much type information there is

Conversely if I must write out the types then I'd prefer it to do the opposite: figure out the trait from the types.

This reminds me of years of people in C++ claiming "writing out the iterator types is good for you..", delaying the deployment of 'range based for', 'auto' ..

dobkeratops commented Jul 26, 2017

most of the time operators are just reference inputs, and the lifetime elision kicks in;
Thinking in C++, I'm used to 2 main cases which are both quite simple:

  • 'accessors' ('self assumption' which Rust successfully allows elision for - this is great) ,
  • and 'no returned references'/'independent result' (harder to give this a catchy name, but basically 'there are no returned references to worry about' - any pointers are fully owned and managed by the result; I appreciate the need to mark up the difference, however I still suspect there might be an easier way to do this: often there's still a gap between my intent ("no escaped references", and the nature of the markup'(ok, show it which references in the inputs correspond to each other, specifically')

So basically the times you actually need to write lifetimes, you're doing something rarer (IMO).

Eliding information in the obvious cases will give you more 'cognitive budget' to spend on the unusual.

Also the information is still there and will surface if you hit compile.

I argue people reading code are more likely to be reading the trait, which does have the full types.

also I argue haskell's split is actually helping readability, regardless of how much type information there is

Conversely if I must write out the types then I'd prefer it to do the opposite: figure out the trait from the types.

This reminds me of years of people in C++ claiming "writing out the iterator types is good for you..", delaying the deployment of 'range based for', 'auto' ..

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.