Skip to content

Destructuring assignment #372

Closed

Description

@glaebhoerl

Given

struct Point { x: int, y: int }
fn returns_point(...) -> Point { ... }
fn returns_tuple(...) -> (int, int) { ... }

it would be nice to be able to do things like

let a; let b;
(a, b) = returns_tuple(...);
let c; let d;
Point { x: c, y: d } = returns_point(...);

and not just in lets, as we currently allow.

Perhaps even:

let my_point: Point;
(my_point.x, my_point.y) = returns_tuple(...);

(Most use cases would likely involve mut variables; but those examples would be longer.)

Related issues from the rust repo: rust-lang/rust#10174 rust-lang/rust#12138

Activity

  1. sbditto85 commented on Dec 30, 2014

    @sbditto85

    Not sure the best way to indicate a vote in the affirmative, but 👍 +1 for this

  2. MattWindsor91 commented on Dec 30, 2014

    @MattWindsor91

    Not sure how rust's RFC process goes, but I assume this needs to be written up in the appropriate RFC format first? I like it, mind.

  3. arthurprs commented on Jan 14, 2015

    @arthurprs

    EDIT: not so fond of it anymore

  4. bstrie commented on Jan 14, 2015

    @bstrie
    Contributor

    @glaebhoerl, how do you expect this to be done? It seems to me that it would require the ability for patterns to appear in arbitrary positions, which strikes me as completely infeasible.

  5. glaebhoerl commented on Jan 14, 2015

    @glaebhoerl
    ContributorAuthor

    @bstrie I don't have any plans myself. There was some discussion of this elsewhere, possibly on the rust repository issues - I think the idea might've been that we could take the intersection of the pattern and expression grammars?

  6. bstrie commented on Jan 14, 2015

    @bstrie
    Contributor

    Assuming that we took the easy route and made this apply only to assignments, we'd also need to take our grammar from LL(k) to LL(infinity). I also don't think that an arbitrarily restricted pattern grammar will make the language easier to read and understand. Finally, the only time when this feature would be useful is when you can't use a new let binding because of scope, in which case the current workaround is to use a temporary. I'm not currently convinced that the gain is worth the cost.

  7. DavidJFelix commented on Jan 19, 2015

    @DavidJFelix

    👍 I've found myself wanting this from time to time, especially in reducing repetition in match statements or normal assignment. Right now I'm using small purpose-built functions instead of this. I haven't considered if it would be possible to abuse a feature like this easily or not.

  8. tstorch commented on Jan 21, 2015

    @tstorch

    I would be thrilled if this would be implemented! Here is a small example why:

    Currently in libcore/str/mod.rs the function maximal_suffix looks like this:

    fn maximal_suffix(arr: &[u8], reversed: bool) -> (uint, uint) {
        let mut left = -1; // Corresponds to i in the paper
        let mut right = 0; // Corresponds to j in the paper
        let mut offset = 1; // Corresponds to k in the paper
        let mut period = 1; // Corresponds to p in the paper
    
        while right + offset < arr.len() {
            let a;
            let b;
            if reversed {
                a = arr[left + offset];
                b = arr[right + offset];
            } else {
                a = arr[right + offset];
                b = arr[left + offset];
            }
            if a < b {
                // Suffix is smaller, period is entire prefix so far.
                right += offset;
                offset = 1;
                period = right - left;
            } else if a == b {
                // Advance through repetition of the current period.
                if offset == period {
                    right += offset;
                    offset = 1;
                } else {
                    offset += 1;
                }
            } else {
                // Suffix is larger, start over from current location.
                left = right;
                right += 1;
                offset = 1;
                period = 1;
            }
        }
        (left + 1, period)
    }

    This could easily look like this:

    fn maximal_suffix(arr: &[u8], reversed: bool) -> (uint, uint) {
        let mut left = -1; // Corresponds to i in the paper
        let mut right = 0; // Corresponds to j in the paper
        let mut offset = 1; // Corresponds to k in the paper
        let mut period = 1; // Corresponds to p in the paper
    
        while right + offset < arr.len() {
            let a;
            let b;
            if reversed {
                a = arr[left + offset];
                b = arr[right + offset];
            } else {
                a = arr[right + offset];
                b = arr[left + offset];
            };
            // Here is the interesting part
            (left, right, offset, period) =
                if a < b {
                    // Suffix is smaller, period is entire prefix so far.
                    (left, right + offset, 1, right - left)
                } else if a == b {
                    // Advance through repetition of the current period.
                    if offset == period {
                        (left, right + offset, 1, period)
                    } else {
                        (left, right, offset + 1, period)
                    }
                } else {
                    // Suffix is larger, start over from current location.
                    (right, right + 1, 1, 1)
                };
            // end intereseting part
        }
        (left + 1, period)
    }

    If we apply, what is currently possible this would be the result:

    fn maximal_suffix(arr: &[u8], reversed: bool) -> (uint, uint) {
        // Corresponds to (i, j, k, p) in the paper
        let (mut left, mut right, mut offset, mut period) = (-1, 0, 1, 1);
    
        while right + offset < arr.len() {
            let (a, b) =
                if reversed {
                    (arr[left + offset], arr[right + offset])
                } else {
                    (arr[right + offset], arr[left + offset])
                };
            (left, right, offset, period) =
                if a < b {
                    // Suffix is smaller, period is entire prefix so far.
                    (left, right + offset, 1, right - left)
                } else if a == b {
                    // Advance through repetition of the current period.
                    if offset == period {
                        (left, right + offset, 1, period)
                    } else {
                        (left, right, offset + 1, period)
                    }
                } else {
                    // Suffix is larger, start over from current location.
                    (right, right + 1, 1, 1)
                };
        }
        (left + 1, period)
    }

    This is easily more readble and I guess readbility of code is a major contribution to code safety and attracts more people to the language and projects written in that laguage.

  9. bombless commented on Jan 21, 2015

    @bombless

    It doesn't feel right...
    If you insist, I think this looks better:

    introduce a, b;
    let (a, b) = returns_tuple(...);
    introduce c, d;
    let Point { x: c, y: d } = returns_point(...);

    Still doesn't feel right, but looks more reasonable.

  10. DavidJFelix commented on Jan 21, 2015

    @DavidJFelix

    So already @bombless this clashes for me as introduce would then become the longest word in rust.

  11. bombless commented on Jan 21, 2015

    @bombless

    @DavidJFelix I don't know, I'd say -1 for this assignment idea.
    And maybe change introduce to intro will make you feel better.

  12. DavidJFelix commented on Jan 21, 2015

    @DavidJFelix

    @bombless, a bit but not much. The point of "let" isn't to offer assignment, it's to introduce the variable. Assignment is done with an assignment operator, "=", If we use both the "=" and let for assignment, it becomes redundant. This is why you see:

    let mut x: uint;
    ...
    x = 123456789;
    

    the point of this issue is that "let" allows us to unravel tuple-packed variables as we declare them and also set their value in one assignment, rather than multiple assignments; but later throughout the program, the assignment operator ceases to do this unraveling and must be done for each variable.

  13. taralx commented on Feb 18, 2015

    @taralx

    So there's two ways to do this. With a desugaring pass (easier) or by actually extending the implementation of ExprAssign in the typechecker and translation. The former works, but I suspect it doesn't produce as nice a set of error messages when types don't match.

    Thoughts?

  14. carllerche commented on Mar 9, 2015

    @carllerche
    Contributor

    I am 👍 for this too

  15. sharpjs commented on Oct 13, 2015

    @sharpjs

    👍 Ran into this today. I'm surprised that it's not implemented already. A function can return a tuple. If I can bind that tuple via a destructuring let, it's perfectly reasonable also to assign that tuple to some bindings I already have.

    let (mut kind, mut ch) = input.classify();
    // ... later ...
    (kind, ch) = another_input.classify();
  16. 92 remaining items

  17. varkor commented on Sep 25, 2018

    @varkor
    Contributor

    If someone wanted to write an RFC taking the points in this thread into account, I'm sure it'd be well-received.

  18. added
    A-syntaxSyntax related proposals & ideas
    A-expressionsTerm language related proposals & ideas
    on Nov 27, 2018
  19. Walther commented on Feb 27, 2019

    @Walther

    Wrote an initial draft of the RFC here based on this thread.

    Comments would be super helpful, thank you!

  20. alexreg commented on Feb 27, 2019

    @alexreg

    Thanks @Walther, that's super.

  21. added a commit that references this issue on Mar 5, 2019
  22. added 2 commits that reference this issue on Dec 22, 2019
  23. varkor commented on Jan 2, 2020

    @varkor
    Contributor

    @fanzier, @cartesiancat and I are going to tackle this feature. We're going to start with an implementation, so we can establish the feasibility, and then follow up with a formal RFC once everything appears to be working.

  24. Boscop commented on Mar 6, 2020

    @Boscop

    @varkor Thanks, I'm looking forward to it, I often wished this was already possible.

  25. varkor commented on Apr 17, 2020

    @varkor
    Contributor

    @fanzier and I have opened an RFC for destructuring assignment. We also have a working prototype at rust-lang/rust#71156.

  26. added a commit that references this issue on Jul 21, 2020
  27. efikarl commented on Dec 4, 2020

    @efikarl

    I'm looking forward to this feature, too.

    For my question:

    use std::net::{ UdpSocket };
    
    
    fn main() -> std::io::Result<()> {
        let mut buf: Vec<u8> = Vec::new();
        let socket = UdpSocket::bind("0.0.0.0:8080")?;
        let (_len, src) = socket.recv_from(&mut buf)?;
        loop {
            // A resp Packet::Data to B
            socket.send_to(&buf, src)?;
            // B send Packet::Ack to A
            let (_len, src) = socket.recv_from(&mut buf)?;
        }
    }

    There will be a error for: 'src' inside the loop: note: #[warn(unused_variables)] on by default

    But if we can destructuring assignment:

    use std::net::{ UdpSocket, SocketAddr};
    
    
    fn main() -> std::io::Result<()> {
        let mut buf: Vec<u8> = Vec::new();
        let socket = UdpSocket::bind("0.0.0.0:8080")?;
        let (mut len, mut src): (usize, SocketAddr) = socket.recv_from(&mut buf)?;
        loop {
            // A resp Packet::Data to B
            socket.send_to(&buf, src)?;
            // B send Packet::Ack to A
            (len, src) = socket.recv_from(&mut buf)?;
        }
    }

    Instead of that as following, something seems verbose:

    fn main() -> std::io::Result<()> {
        let mut buf: Vec<u8> = Vec::new();
        let socket = UdpSocket::bind("0.0.0.0:8080")?;
        let (mut _len, mut src): (usize, SocketAddr) = socket.recv_from(&mut buf)?;
        loop {
            // A resp Packet::Data to B
            socket.send_to(&buf, src)?;
            // B send Packet::Ack to A
            let (_len, isrc) = socket.recv_from(&mut buf)?;
            src = isrc;
        }
    }
  28. added a commit that references this issue on Dec 8, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-expressionsTerm language related proposals & ideasA-syntaxSyntax related proposals & ideasT-langRelevant to the language team, which will review and decide on the RFC.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions