Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Unsafe enums #724

Closed
wants to merge 6 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions text/0000-unsafe-enums.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
- Start Date: 2014-01-23
- RFC PR: (leave this empty)
- Rust Issue: (leave this empty)

# Summary

Add an unsafe enum which is an enum without a discriminant.

# Motivation

When working with FFI, many C libraries will take advantage of unions. Unfortunately Rust has no way
to represent this sanely, so developers are forced to write duplicated struct definitions for each
union variant and do a lot of ugly transmuting. Unsafe enums are effectively unions, and allow FFI
that uses unions to be significantly easier. The syntax chosen here replicates existing syntax without adding new keywords and is thus backwards compatible with existing code.

# Detailed design

An unsafe enum is equivalent to a safe enum except that it does not have a discriminant.

## Declaring an unsafe enum

```rust
unsafe enum MyEnum {
Variant1(c_int),
Variant2(*mut c_char),
Variant3 {
x: f32,
y: f32,
},
}
```

## Instantiating unsafe enums
This is completely safe.
```rust
let foo = Variant1(5);
```

## Destructuring

Due to the lack of a discriminant, destructuring becomes irrefutable, but also unsafe. Therefore all destructuring needs to be in unsafe functions or blocks. Because destructuring is irrefutable you can directly destructure unsafe enums which isn't possible with safe enums:
```rust
unsafe {
let Variant1(bar) = foo;
}
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be kind of annoying for when you want to only put the minimum number of non-unsafe things inside the unsafe block; you’d have to write something like this:

let x;
unsafe {
    let Variant1(bar) = foo;
    x = bar;
}
// use `x`

because unsafe creates its own scope. I don’t really have a better suggestion, but it’s good Rust style to put only actually unsafe stuff inside unsafe blocks, and this would make that a little harder.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps something like let unsafe { Variant1(bar) } = foo;? I'm not sure how feasible that would be to implement though.
If we went with the fancy restrictive trait as suggested by @eddyb then we'd be able to remove the unsafe blocks entirely making the resulting code much more concise.

When using `match` you can only have a single irrefutable pattern. However, patterns with values inside of them or conditionals are refutable and can therefore be combined.
```rust
unsafe {
// Legal
match foo {
Variant2(x) => ...,
}
match foo {
Variant1(5) => ...,
Variant1(x) if x < -7 => ...,
Variant1(x) => ...,
}
// Illegal
match foo {
Variant1(x) => ...,
Variant2(x) => ...,
}
match foo {
Variant1(x) => ...,
_ => ...,
}
}
```
`if let` and `while let` are irrefutable unless the pattern has a value inside. Because irrefutable `if let` and `while let` patterns are currently illegal for enums in Rust, they will continue to be illegal for unsafe enums.
```rust
unsafe {
// Legal
if let Variant1(5) = foo {...}
while let Variant1(7) = foo {...}
// Illegal
if let Variant1(x) = foo {...}
while let Variant2(y) = foo {...}
}
```

## Requirements on variants

Due to the lack of a discriminant there is no way for Rust to know which variant is currently
initialized, and thus all variants of an unsafe enum are required to be `Copy` or at the very least
not `Drop`.

# Drawbacks

Adding unsafe enums adds more complexity to the language through a separate kind of enum with its own restrictions and behavior.

# Alternatives

* Continue to not provide untagged unions and make life difficult for people doing FFI.
* Add an entirely separate type with a keyword such as `union`.

# Unresolved questions

* Should we require that the variants are merely `!Drop` or should we require `Copy`, or, as @eddyb suggested, should we add an opt in built in trait that represents a type where every possible bit pattern is a valid value for that type? With the latter option we would be able to make destructuring completely safe.