rust-lang / rust-lang/rfcs

Unsized receivers

Open
#1,611 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

T-lang
Dominant language
Markdown
Stars
6.6k
Forks
1.7k
Avg merge
16h 14m
Merged PRs (30d)
1

Description

Proposal

Allow receivers automatically "unsize" self. For example, given:

trait Trait {}
struct View<'a>(&'a mut Wrapper<Trait + 'a>);
struct Wrapper<A: ?Sized + Trait>(A);

Desugar:

impl<T: ?Sized + Trait> Wrapper<T> {
    // Unsizing receiver
    fn view<'b>(self: &'b mut Wrapper<Trait + 'b>) -> View<'b> {
        View(self)
    }
}

To:

impl<T: ?Sized + Trait> Wrapper<T> {
    fn view<'b>(&mut self) -> View<'b>
        where Self: Unsize<Wrapper<Trait + 'b>>
    {
        View(self as &mut Wrapper<Trait + 'b>)
    }
}

Motivation

This can be useful for achieving object safety. That is, given the Self: Unsize<...> bound, one can call view on either *&mut Wrapper<T: Trait> or &mut Wrapper<Trait>. Unfortunately,

  1. If someone were to come across this Self: Unsize<...> bound in a struct's documentation, they'd probably have no idea what it means.
  2. The fact that Self: Unsize<...> is the correct bound to achieve this is non-obvious. For example, in a forum post, both the compiler and @bluss told a user that a method needed a Self: Sized when, really, Self: Unsize<Trait> was the optimal (most flexible) bound (note: obviously, Unsize<Trait> isn't the best solution for now because it's unstable).

For future reference, the code under discussion was:

trait Inner {
    fn inner_add(&self, &Outer);
}

trait Outer {
    fn inner(&self) -> &Inner;

    fn add(&self) {
        self.inner().inner_add(self); 
    } 
}

Where the question was "I don't understand why the Sized is needed, since I'm only ever using references."

The suggested fix was:

trait Inner {
    fn inner_add(&self, &Outer);
}

trait Outer {
    fn inner(&self) -> &Inner;

    fn add(&self) where Self: Sized {
        self.inner().inner_add(self); 
    } 
}

The most flexible fix (once stable) would be:

#![feature(unsize)]
use std::marker::Unsize;

trait Inner {
    fn inner_add(&self, &Outer);
}

trait Outer {
    fn inner(&self) -> &Inner;

    fn add<'a>(&'a self)
        where Self: Unsize<Outer + 'a>,
    {
        self.inner().inner_add(self);
    }
}

With the suggested sugar, this would be reduced to:

trait Inner {
    fn inner_add(&self, &Outer);
}

trait Outer {
    fn inner(&self) -> &Inner;

    fn add(self: &Outer) {
        self.inner().inner_add(self);
    }
}

Which, IMO, is much cleaner.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the proposal's receiver examples and desugaring, then review the linked forum discussion for the motivation around object safety and Unsize bounds. Determine how the proposed receiver syntax should behave for sized and unsized implementors, and use those cases to define completion criteria; the issue names no files or tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.