blob: d9c37328bef0030631a88fdec9f9510b75984147 [file] [log] [blame]
// Part of the Crubit project, under the Apache License v2.0 with LLVM
// Exceptions. See /LICENSE for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#ifndef CRUBIT_SUPPORT_INTERNAL_RETURN_VALUE_SLOT_H_
#define CRUBIT_SUPPORT_INTERNAL_RETURN_VALUE_SLOT_H_
#include <memory>
#include <utility>
namespace crubit {
// `ReturnValueSlot<T>` provides a slot that can store a move-only return
// value. This class is used to return non-`#[repr(C)]` structs from Rust
// into C++ in a way that is compatible with the ABI of `extern "C"` Rust
// thunks.
//
// An example will help to illustrate the purpose of this class:
//
// ```rs
// pub struct SomeStruct(...);
// pub fn foo(arg1: i32, arg2: i32) -> SomeStruct { unimplemented!() }
// ```
//
// The generated C++ header will look like this:
//
// ```cc
// inline SomeStruct foo(int32_t arg1, int32_t arg2) {
// /* 1 */ crubit::ReturnValueSlot<SomeStruct> __ret;
// /* 2 */ __rust_thunk_for_foo(arg1, arg2, __ret.GetSlotPtr());
// /* 3 */ return __ret.AssumeInitAndTakeValue();
// }
// }
// ```
//
// `ReturnValueSlot` helps to coordinate when C++ constructors and destructors
// run in the example above:
// - `SomeStruct`'s constructor should *not* run on line 1.
// - Rust thunk can populates the return slot on line 2.
// The Rust thunk may panic without populating the return slot - in this
// case nothing should operate on the uninitialized `SomeStruct` value
// (this is accomplished by ReturnValueSlot having an empty/no-op destructor)
// - `SomeStruct`'s move constructor will run on line 3 (moving the return value
// out of `ReturnValueSlot::value_`, and then destructing the moved-away
// `ReturnValueSlot::value_`).
//
// Behavior of `ReturnValueSlot<T>` in steps 1 and 2 is identical to
// `MaybeUninit<T>` in Rust, but the behavior on line 3 is a bit different:
// there is an extra call to a move constructor in C++, but there are no move
// constructors in Rust.
template <typename T>
class ReturnValueSlot {
public:
// Creates `ReturnValueSlot` in an uninitialized state.
ReturnValueSlot() {
// Leaving `value_` uninitialized / not invoking any constructor of `T`.
}
// Gets a pointer to the slot where the return value may be written.
//
// SAFETY REQUIREMENTS:
// - Caller should not read from the returned pointer before the value has
// been initialized.
// - Caller should only write to the returned pointer while the
// `ReturnValueSlot` is in an uninitialized state (i.e. care should be taken
// to avoid writing to the slot twice, potentially overwriting a value
// without calling its destructor).
T* Get() { return &value_; }
// Destructively takes and returns the contained value.
//
// Leaves the value contained in `ReturnValueSlot` uninitialized.
//
// SAFETY REQUIREMENTS: The contained value is initialized.
T AssumeInitAndTakeValue() && {
T return_value(std::move(value_));
std::destroy_at(&value_);
return return_value;
}
// SAFETY REQUIREMENTS: The value contained in `other` must be initialized
// (but may be moved-from).
ReturnValueSlot(ReturnValueSlot&& other) { value_ = std::move(other.value_); }
// Does not destroy the contained value.
//
// Before `~ReturnValueSlot()` is invoked, the contained value should be
// destroyed by the user (typically, by calling `AssumeInitAndTakeValue`). If
// the contained value is left initialized by the time `~ReturnValueSlot()`
// runs, the value is leaked.
~ReturnValueSlot() {
// Not destroying or otherwise using `value_`.
}
ReturnValueSlot(const ReturnValueSlot&) = delete;
ReturnValueSlot& operator=(const ReturnValueSlot&) = delete;
ReturnValueSlot& operator=(ReturnValueSlot&&) = delete;
private:
// Use a union to allow us full manual control of the initialization and
// destruction of the value.
union {
T value_;
};
};
} // namespace crubit
#endif // CRUBIT_SUPPORT_INTERNAL_RETURN_VALUE_SLOT_H_