Почему "Self" нельзя использовать для обозначения варианта перечисления в теле метода?

Этот вопрос сейчас устарел, потому что эта функция была реализована. Связанный ответ.


Следующий код Rust не компилируется:

enum Foo {
    Bar,
}

impl Foo {
    fn f() -> Self {
        Self::Bar
    }
}

Сообщение об ошибке смущает меня:

error[E0599]: no associated item named 'Bar' found for type 'Foo' in the current scope
 --> src/main.rs:7:9
  |
7 |         Self::Bar
  |         ^^^^^^^^^

Проблема может быть исправлена с помощью Foo вместо Self, но это кажется мне странным, поскольку Self должен ссылаться на реализуемый тип (игнорируя черты), который в этом случае Foo.

enum Foo {
    Bar,
}

impl Foo {
    fn f() -> Self {
        Foo::Bar
    }
}

Почему нельзя использовать Self в этой ситуации? Где именно можно использовать Self *? Можно ли использовать что-либо еще, чтобы избежать повторения имени типа в теле метода?

* I'm ignoring usage in traits, where [TG49] refers to whatever type implements the trait.

Ответ 1

Важно отметить, что в сообщении об ошибке упоминается связанный элемент. enum Foo { Baz } не имеет связанных предметов. Черта может иметь связанный элемент:

trait FooBaz { type Baz }
//             ^~~~~~~~ - associated item

Подвести итоги:

Почему нельзя использовать Self в этой ситуации?

Из-за этой проблемы. RFC 2338 еще не был реализован.

Кажется, что Self действует как псевдоним типа, хотя и с некоторыми изменениями.

Где именно можно использовать Self?

Самость может быть использована только в чертах и impl. Этот код:

struct X {
    f: i32,
    x: &Self,
}

Выводит следующее:

error[E0411]: cannot find type 'Self' in this scope
 --> src/main.rs:3:9
  |
3 |     x: &Self,
  |         ^^^^ 'Self' is only available in traits and impls

Возможно, это временная ситуация и может измениться в будущем!

Точнее, Self следует использовать только как часть сигнатуры метода (например, fn self_in_self_out(&self) → Self) или для доступа к связанному типу:

enum Foo {
    Baz,
}

trait FooBaz {
    type Baz;

    fn b(&self) -> Self::Baz; // Valid use of 'Self' as method argument and method output
}


impl FooBaz for Foo {
    type Baz = Foo;

    fn b(&self) -> Self::Baz {
        let x = Foo::Baz as Self::Baz; // You can use associated type, but it just a type
        x
    }
}

Я думаю, что user4815162342 покрыл остальную часть ответа лучше всего.

Ответ 2

Если имя перечисления Foo на самом деле длинное, и вы хотите избежать повторения его в реализации, у вас есть два варианта:

  • use LongEnumName as Short на уровне модуля. Это позволит вам вернуть Short::Bar в конце f.
  • use LongEnumName::* на уровне модуля, что позволяет еще короче Bar.

Если вы опустите pub, импорт будет внутренним и не повлияет на открытый API модуля.

Ответ 3

Конструкторы Enum!= связанные элементы.

Это известный issue, но он не ожидается, что он будет исправлен, по крайней мере, в обозримом будущем. Из того, что я собрал, нетрудно просто позволить этому работать; на данный момент более вероятно, что соответствующая документация или сообщение об ошибке будут улучшены.

Существует небольшая документация, которую я мог бы найти по теме связанных элементов в целом; Однако в книге Rust Book есть глава о связанных типах. Кроме того, есть много хороших ответов о Self в этом связанном вопросе.

Ответ 4

Существует экспериментальная функция, которая позволит вашему примеру работать без каких-либо других изменений. Вы можете попробовать его в ночной сборке Rust, добавив это в свой основной файл:

#![feature(type_alias_enum_variants)]

Вы можете следить за ходом функции к стабилизации в ее проблеме отслеживания.

Ответ 5

Теперь это возможно начиная с версии 1.37.