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

Support defining C-compatible variadic functions in Rust #2137

Merged
merged 25 commits into from
Sep 29, 2017
Merged
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
019dff4
Support defining C-compatible variadic functions in Rust
joshtriplett Sep 3, 2017
3039f17
Add VaArg for size_t and ssize_t; sort and group the VaArg impls better
joshtriplett Sep 3, 2017
12eb69f
Allow calling extern "C" functions that accept a `va_list`
joshtriplett Sep 3, 2017
e6b5cfb
Drop variadic closures
joshtriplett Sep 3, 2017
e9fe291
Fix typo in sample code
joshtriplett Sep 3, 2017
247991e
Drop impls for libc, mention that type aliases cover this
joshtriplett Sep 3, 2017
0cca2ce
Make VaArg an unsafe trait
joshtriplett Sep 3, 2017
3161c45
Fix typo in sample code
joshtriplett Sep 3, 2017
7b7b3ff
Drop VaList::start; name the VaList argument instead, to improve life…
joshtriplett Sep 3, 2017
9d060c4
Declare args as `mut`
joshtriplett Sep 3, 2017
3769670
Rework the alternatives; the new syntax isn't the "alternative" anymore
joshtriplett Sep 3, 2017
63b5545
Fix another reference to VaList::start
joshtriplett Sep 3, 2017
5437ab2
Clarify the description of argument promotion
joshtriplett Sep 5, 2017
7bd8d7d
Get rid of the VaArg trait
joshtriplett Sep 5, 2017
96f80ac
Don't impl `Drop` directly; just talk about the necessary drop semantics
joshtriplett Sep 5, 2017
18a8b36
Fix `extern "C"` function declarations
joshtriplett Sep 6, 2017
7e5698e
Allow `VaList::arg` to return any type usable in an `extern "C" fn` s…
joshtriplett Sep 6, 2017
105f764
Use `extern type` to declare `VaList` (see RFC 1861)
joshtriplett Sep 7, 2017
6bdb95c
Move the mention of language items to the reference-level explanation
joshtriplett Sep 7, 2017
1546c82
Use the rustdoc-style `/* fields omitted */` syntax to declare VaList
joshtriplett Sep 7, 2017
eca3eae
Require that the function have at least one non-variadic argument
joshtriplett Sep 8, 2017
e3d1f5c
Add some clarifications on drop handling, and non-standard behavior
joshtriplett Sep 8, 2017
b2acc33
Stop using Clone; implement copy safely via a closure
joshtriplett Sep 13, 2017
4042834
Add an unresolved question on support for non-native ABIs
joshtriplett Sep 15, 2017
eb9c392
RFC 2137: Support defining C-compatible variadic functions in Rust
aturon Sep 29, 2017
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
Prev Previous commit
Next Next commit
Stop using Clone; implement copy safely via a closure
Huge thanks to Without Boats for help working out the details here.
  • Loading branch information
joshtriplett committed Sep 13, 2017
commit b2acc33c3bb7c2a575647ca109338b281fc32af2
45 changes: 20 additions & 25 deletions text/0000-variadic.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,17 @@ To access the arguments, Rust provides the following public interfaces in
/// underlying C `va_list`. Opaque.
pub struct VaList<'a> { /* fields omitted */ }

// Note: the lifetime on VaList is invariant
impl<'a> VaList<'a> {
/// Extract the next argument from the argument list. T must have a type
/// usable in an FFI interface.
pub unsafe fn arg<T>(&mut self) -> T;
}

impl<'a> Clone for VaList<'a>;
/// Copy the argument list. Destroys the copy after the closure returns.
pub fn copy<'ret, F, T>(&self, F) -> T
where
F: for<'copy> FnOnce(VaList<'copy>) -> T, T: 'ret;
}
```

The type returned from `VaList::arg` must have a type usable in an `extern "C"`
Expand All @@ -98,7 +102,8 @@ platform-specific representation.
A variadic function may pass the `VaList` to another function. However, the
lifetime attached to the `VaList` will prevent the variadic function from
returning the `VaList` or otherwise allowing it to outlive that call to the
variadic function.
variadic function. Similarly, the closure called by `copy` cannot return the
`VaList` passed to it or otherwise allow it to outlive the closure.

A function declared with `extern "C"` may accept a `VaList` parameter,
corresponding to a `va_list` parameter in the corresponding C function. For
Expand All @@ -117,7 +122,7 @@ extern "C" {
Note that, per the C semantics, after passing `VaList` to these functions, the
caller can no longer use it, hence the use of the `VaList` type to take
ownership of the object. To continue using the object after a call to these
functions, pass a clone of it instead.
functions, use `VaList::copy` to pass a copy of it instead.

Conversely, an `unsafe extern "C"` function written in Rust may accept a
`VaList` parameter, to allow implementing the `v` variants of such functions in
Expand Down Expand Up @@ -169,31 +174,21 @@ Compiling and linking these two together will produce a program that prints:
LLVM already provides a set of intrinsics, implementing `va_start`, `va_arg`,
`va_end`, and `va_copy`. The compiler will insert a call to the `va_start`
intrinsic at the start of the function to provide the `VaList` argument (if
used). The implementation of `VaList::arg` will call `va_arg`. The
implementation of `Clone` for `VaList` wil call `va_copy`. The compiler will
ensure that a call to `va_end` occurs exactly once on every `VaList` at an
appropriate time, similar to drop semantics. (This may occur via an
implementation of `Drop`, but this must take into account the semantics of
passing a `VaList` to or from an `extern "C"` function.)
used), and a matching call to the `va_end` intrinsic on any exit from the
function. The implementation of `VaList::arg` will call `va_arg`. The
implementation of `VaList::copy` wil call `va_copy`, and then `va_end` after
the closure exits.

`VaList` may become a language item (`#[lang="VaList"]`) to attach the
appropriate compiler handling.

The compiler may need to handle the type `VaList` specially, in order to
provide the desired drop semantics at FFI boundaries. In particular, some
platforms pass `va_list` by value, and others pass it by pointer; the standard
leaves unspecified whether changes made in the called function appear in the
caller's copy. Rust must match the underlying C semantics, to allow passing
VaList values between C and Rust. To avoid forcing variadic functions to cope
with these platform-specific differences, the compiler should ensure that the
type behaves as if it has `Drop` semantics, and has `va_end` called on the
original `VaList` as well as any clones of it. For instance, passing a `VaList`
to a `vprintf` function as declared in this RFC semantically passes ownership
of that `VaList`, and prevents calling the `arg` function again in the caller,
but it remains the caller's responsibility to call `va_end`, so the compiler
needs to insert the appropriate drop glue at the FFI boundary. Conversely,
functions accepting a `VaList` argument must not drop it themselves, since the
caller will drop it instead.
provide the desired parameter-passing semantics at FFI boundaries. In
particular, some platforms define `va_list` as a single-element array, such
that declaring a `va_list` allocates storage, but passing a `va_list` as a
function parameter occurs by pointer. The compiler must arrange to handle both
receiving and passing `VaList` parameters in a manner compatible with the C
ABI.

The C standard requires that the call to `va_end` for a `va_list` occur in the
same function as the matching `va_start` or `va_copy` for that `va_list`. Some
Copy link

@fstirlitz fstirlitz Sep 8, 2017

Choose a reason for hiding this comment

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

The standard only says

The va_end macro facilitates a normal return from the function whose variable argument list was referred to by the expansion of the va_start macro, or the function containing the expansion of the va_copy macro, that initialized the va_list ap. [...]

[I]f the va_end macro is not invoked before the return, the behavior is undefined.

which I take to mean that you're allowed to pass a va_list *pap to a function which will call va_end(*pap) for you. Although I don't imagine this ever happening at cross-module boundary, so you might get away with ignoring this possibility.

Forget what I said.

Each invocation of the va_start and va_copy macros shall be matched by a corresponding invocation of the va_end macro in the same function.

Expand All @@ -219,7 +214,7 @@ the appropriate argument types provided by the caller, based on whatever
arbitrary runtime information determines those types. However, in this regard,
this feature provides no more unsafety than the equivalent C code, and in fact
provides several additional safety mechanisms, such as automatic handling of
type promotions, lifetimes, copies, and destruction.
type promotions, lifetimes, copies, and cleanup.

# Rationale and Alternatives
[alternatives]: #alternatives
Expand Down