blob: 923838b21c8e6a06aedd8731add23f634c6b5844 [file]
// Protocol Buffers - Google's data interchange format
// Copyright 2023 Google LLC. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
//! Operating on borrowed data owned by a message is a central concept in
//! Protobuf (and Rust in general). The way this is normally accomplished in
//! Rust is to pass around references and operate on those. Unfortunately,
//! references come with two major drawbacks:
//!
//! * We must store the value somewhere in the memory to create a reference to it. The value must be
//! readable by a single load. However for Protobuf fields it happens that the actual memory
//! representation of a value differs from what users expect and it is an implementation detail
//! that can change as more optimizations are implemented. For example, rarely accessed `int64`
//! fields can be represented in a packed format with 32 bits for the value in the common case.
//! Or, a single logical value can be spread across multiple memory locations. For example,
//! presence information for all the fields in a protobuf message is centralized in a bitset.
//! * We cannot store extra data on the reference that might be necessary for correctly manipulating
//! it (and custom-metadata DSTs do not exist yet in Rust). Concretely, messages, string, bytes,
//! and repeated fields in UPB need to carry around an arena parameter separate from the data
//! pointer to enable mutation (for example adding an element to a repeated field) or potentially
//! to enable optimizations (for example referencing a string value using a Cord-like type instead
//! of copying it if the source and target messages are on the same arena already). Mutable
//! references to messages have one additional drawback: Rust allows users to indiscriminately run
//! a bytewise swap() on mutable references, which could result in pointers to the wrong arena
//! winding up on a message. For example, imagine swapping a submessage across two root messages
//! allocated on distinct arenas A and B; after the swap, the message allocated in A may contain
//! pointers from B by way of the submessage, because the swap does not know to fix up those
//! pointers as needed. The C++ API uses message-owned arenas, and this ends up resembling
//! self-referential types, which need `Pin` in order to be sound. However, `Pin` has much
//! stronger guarantees than we need to uphold.
//!
//! These drawbacks put the "idiomatic Rust" goal in conflict with the
//! "performance", "evolvability", and "safety" goals. Given the project design
//! priorities we decided to not use plain Rust references. Instead, we
//! implemented the concept of "proxy" types. Proxy types are a reference-like
//! indirection between the user and the internal memory representation.
use crate::__internal::{Private, SealedInternal};
/// NOTE: This type is primarily an implementation detail in support of trait resolution, and
/// application code should very rarely write this type.
///
/// An owned Protobuf entity or field type that can be accessed through reference-like proxies.
///
/// In standard Rust, shared and exclusive access to borrowed data is provided via native
/// references (`&T` and `&mut T`). However, standard references don't provide sufficient power
/// to hide implementation details that Protobuf wants to take advantage of: for example, handing
/// out `&mut` always allows for `std::mem::swap`, which precludes certain optimizations across
/// parenting relationships. It also prevents optimizations like root messages and child messages
/// of the same type having different layouts.
///
/// To provide safe, ergonomic, and zero-cost or low-cost access across these representations,
/// Protobuf uses "proxy" types:
/// - [`View<'msg, T>`](View): A proxy type that provides shared, read-only access (analogous to
/// `&'msg T`).
/// - [`Mut<'msg, T>`](Mut): A proxy type that provides exclusive mutable access (analogous to
/// `&'msg mut T`).
///
/// An implementor of `ProtoObject` represents the *owned* target type (for example, a generated
/// message struct `MyMessage`, primitive types like `i32`, `Repeated<T>`, `Map<K, V>`,
/// `ProtoBytes`, or `ProtoString`). It is **not** the proxy itself.
///
/// # Writing User Code
///
/// In general, application code should rarely ever need to write the type `ProtoObject`.
///
/// - **Concrete usage**: Work directly with the owned message types (e.g. `MyMessage`), concrete
/// view types (e.g. `MyMessageView<'msg>`, `MyMessageMut<'msg>`), or the type aliases
/// [`View<'msg, T>`](View) and [`Mut<'msg, T>`](Mut).
/// - **Generic functions**: When writing functions that operate generically on messages or borrows
/// of messages, prefer higher-level traits such as [`Message`](crate::Message), [`AsView`],
/// [`IntoView`], [`AsMut`], or [`IntoMut`]. For example: ```ignore fn process_message(msg: impl
/// AsView<Proxied = MyMessage>) { let view = msg.as_view(); // ... } ```
///
/// `ProtoObject` is primarily a foundational plumbing trait connecting owned types with their
/// associated proxy representations for the Protobuf runtime and code generator.
pub trait ProtoObject: SealedInternal + AsView<Proxied = Self> + Sized + 'static {
/// The proxy type that provides shared access to a `T`, like a `&'msg T`.
///
/// Most code should use the type alias [`View`].
type View<'msg>: AsView<Proxied = Self> + IntoView<'msg>;
}
/// Deprecated alias for [`ProtoObject`].
#[deprecated(note = "Use `ProtoObject` instead.")]
pub use self::ProtoObject as Proxied;
/// NOTE: This type is primarily an implementation detail in support of trait resolution, and
/// application code should very rarely write this type.
///
/// A [`ProtoObject`] that can also be accessed mutably through a reference-like proxy.
///
/// An instance of a `MutProtoObject` can be accessed mutably via `MutProtoObject::Mut`
/// and immutably via `ProtoObject::View`.
///
/// `MutProtoObject` is implemented by message, map and repeated field types.
pub trait MutProtoObject:
SealedInternal + ProtoObject + AsMut<MutProxied = Self> + 'static
{
/// The proxy type that provides exclusive mutable access to a `T`, like a
/// `&'msg mut T`.
///
/// Most code should use the type alias [`Mut`].
type Mut<'msg>: AsMut<MutProxied = Self> + IntoMut<'msg> + IntoView<'msg>;
}
/// Deprecated alias for [`MutProtoObject`].
#[deprecated(note = "Use `MutProtoObject` instead.")]
pub use self::MutProtoObject as MutProxied;
/// A proxy type that provides shared access to a `T`, like a `&'msg T`.
///
/// This is more concise than fully spelling the associated type.
#[allow(dead_code)]
pub type View<'msg, T> = <T as ProtoObject>::View<'msg>;
/// A proxy type that provides exclusive mutable access to a `T`, like a
/// `&'msg mut T`.
///
/// This is more concise than fully spelling the associated type.
#[allow(dead_code)]
pub type Mut<'msg, T> = <T as MutProtoObject>::Mut<'msg>;
/// Used to semantically do a cheap "to-reference" conversion. This is
/// implemented on both owned `ProtoObject` types as well as view and mut proxy
/// types.
///
/// On a view proxy this will behave as a reborrow into a shorter lifetime.
#[diagnostic::on_unimplemented(
message = "the trait `AsView` is not implemented for `{Self}`",
note = "consider calling `.as_view()`, or changing the fn to accept \
`impl AsView<Proxied = T>`"
)]
pub trait AsView: SealedInternal {
type Proxied: ProtoObject;
/// Converts a borrow into a `View` with the lifetime of that borrow.
///
/// In non-generic code we don't need to use `as_view` because the proxy
/// types are covariant over `'msg`. However, generic code conservatively
/// treats `'msg` as [invariant], therefore we need to call
/// `as_view` to explicitly perform the operation that in concrete code
/// coercion would perform implicitly.
///
/// For example, the call to `.as_view()` in the following snippet
/// wouldn't be necessary in concrete code:
/// ```ignore
/// fn reborrow<'a, 'b, T>(x: &'b View<'a, T>) -> View<'b, T>
/// where 'a: 'b, T: ProtoObject
/// {
/// x.as_view()
/// }
/// ```
///
/// [invariant]: https://doc.rust-lang.org/nomicon/subtyping.html#variance
fn as_view(&self) -> View<'_, Self::Proxied>;
}
impl<T: ProtoObject> AsView for &T {
type Proxied = T::Proxied;
fn as_view(&self) -> View<'_, Self::Proxied> {
(**self).as_view()
}
}
impl<T: ProtoObject> AsView for &mut T {
type Proxied = T::Proxied;
fn as_view(&self) -> View<'_, Self::Proxied> {
(**self).as_view()
}
}
/// Used to turn another 'borrow' into a view proxy.
///
/// On a mut proxy this borrows to a View (semantically matching turning a `&mut
/// T` into a `&T`).
///
/// On a view proxy this will behave as a reborrow into a shorter lifetime
/// (semantically matching a `&'a T` into a `&'b T` where `'a: 'b`).
#[diagnostic::on_unimplemented(
message = "the trait `IntoView` is not implemented for `{Self}` (consider calling `.as_view()` \
or borrowing)",
note = "consider calling `.as_view()` or `.into_view()`"
)]
pub trait IntoView<'msg>: SealedInternal + AsView {
/// Converts into a `View` with a potentially shorter lifetime.
///
/// In non-generic code we don't need to use `into_view` because the proxy
/// types are covariant over `'msg`. However, generic code conservatively
/// treats `'msg` as [invariant], therefore we need to call
/// `into_view` to explicitly perform the operation that in concrete
/// code coercion would perform implicitly.
///
/// ```ignore
/// fn reborrow_generic_view_into_view<'a, 'b, T>(
/// x: View<'a, T>,
/// y: View<'b, T>,
/// ) -> [View<'b, T>; 2]
/// where
/// T: MutProtoObject,
/// 'a: 'b,
/// {
/// // `[x, y]` fails to compile because `'a` is not the same as `'b` and the `View`
/// // lifetime parameter is (conservatively) invariant.
/// // `[x.as_view(), y]` fails because that borrow cannot outlive `'b`.
/// [x.into_view(), y]
/// }
/// ```
///
/// [invariant]: https://doc.rust-lang.org/nomicon/subtyping.html#variance
fn into_view<'shorter>(self) -> View<'shorter, Self::Proxied>
where
'msg: 'shorter;
}
impl<'msg, T: ProtoObject> IntoView<'msg> for &'msg T {
fn into_view<'shorter>(self) -> View<'shorter, T>
where
'msg: 'shorter,
{
(*self).as_view()
}
}
impl<'msg, T: ProtoObject> IntoView<'msg> for &'msg mut T {
fn into_view<'shorter>(self) -> View<'shorter, T>
where
'msg: 'shorter,
{
(*self).as_view()
}
}
/// Used to semantically do a cheap "to-mut-reference" conversion. This is
/// implemented on both owned `ProtoObject` types as well as mut proxy types.
///
/// On a mut proxy this will behave as a reborrow into a shorter lifetime.
#[diagnostic::on_unimplemented(
message = "the trait `AsMut` is not implemented for `{Self}`",
note = "consider calling `.as_mut()`, or changing the fn to accept \
`impl AsMut<MutProxied = T>`"
)]
pub trait AsMut: SealedInternal + AsView<Proxied = Self::MutProxied> {
type MutProxied: MutProtoObject;
/// Converts a borrow into a `Mut` with the lifetime of that borrow.
fn as_mut(&mut self) -> Mut<'_, Self::MutProxied>;
}
impl<T: MutProtoObject> AsMut for &mut T {
type MutProxied = T::MutProxied;
fn as_mut(&mut self) -> Mut<'_, Self::MutProxied> {
(*self).as_mut()
}
}
/// Used to turn another 'borrow' into a mut proxy.
///
/// On a mut proxy this will behave as a reborrow into a shorter lifetime
/// (semantically matching a `&mut 'a T` into a `&mut 'b T` where `'a: 'b`).
#[diagnostic::on_unimplemented(
message = "the trait `IntoMut` is not implemented for `{Self}` (consider calling `.as_mut()` \
or mutably borrowing)",
note = "consider calling `.as_mut()` or `.into_mut()`"
)]
pub trait IntoMut<'msg>: SealedInternal + AsMut {
/// Converts into a `Mut` with a potentially shorter lifetime.
///
/// In non-generic code we don't need to use `into_mut` because the proxy
/// types are covariant over `'msg`. However, generic code conservatively
/// treats `'msg` as [invariant], therefore we need to call
/// `into_mut` to explicitly perform the operation that in concrete code
/// coercion would perform implicitly.
///
/// ```ignore
/// fn reborrow_generic_mut_into_mut<'a, 'b, T>(x: Mut<'a, T>, y: Mut<'b, T>) -> [Mut<'b, T>; 2]
/// where
/// T: ProtoObject,
/// 'a: 'b,
/// {
/// // `[x, y]` fails to compile because `'a` is not the same as `'b` and the `Mut`
/// // lifetime parameter is (conservatively) invariant.
/// // `[x.as_mut(), y]` fails because that borrow cannot outlive `'b`.
/// [x.into_mut(), y]
/// }
/// ```
///
/// [invariant]: https://doc.rust-lang.org/nomicon/subtyping.html#variance
fn into_mut<'shorter>(self) -> Mut<'shorter, Self::MutProxied>
where
'msg: 'shorter;
}
impl<'msg, T: MutProtoObject> IntoMut<'msg> for &'msg mut T {
fn into_mut<'shorter>(self) -> Mut<'shorter, T>
where
'msg: 'shorter,
{
(*self).as_mut()
}
}
/// A value to `ProtoObject`-value conversion that consumes the input value.
///
/// All setter functions accept types that implement `IntoProxied`. The purpose
/// of `IntoProxied` is to allow setting arbitrary values on Protobuf fields
/// with the minimal number of copies.
///
/// This trait must not be implemented on types outside the Protobuf codegen and
/// runtime. We expect it to change in backwards incompatible ways in the
/// future.
pub trait IntoProxied<T: ProtoObject> {
#[doc(hidden)]
fn into_proxied(self, _private: Private) -> T;
}
impl<T: ProtoObject> IntoProxied<T> for T {
fn into_proxied(self, _private: Private) -> T {
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use googletest::prelude::*;
#[derive(Debug, Default, PartialEq)]
struct MyProxied {
val: String,
}
impl MyProxied {
fn as_view(&self) -> View<'_, Self> {
MyProxiedView { my_proxied_ref: self }
}
fn as_mut(&mut self) -> Mut<'_, Self> {
MyProxiedMut { my_proxied_ref: self }
}
}
impl SealedInternal for MyProxied {}
impl ProtoObject for MyProxied {
type View<'msg> = MyProxiedView<'msg>;
}
#[gtest]
#[allow(deprecated)]
fn test_deprecated_proxied_alias() {
fn check_proxied<T: Proxied>() {}
check_proxied::<MyProxied>();
fn check_mut_proxied<T: MutProxied>() {}
check_mut_proxied::<MyProxied>();
}
impl AsView for MyProxied {
type Proxied = Self;
fn as_view(&self) -> MyProxiedView<'_> {
self.as_view()
}
}
impl MutProtoObject for MyProxied {
type Mut<'msg> = MyProxiedMut<'msg>;
}
impl AsMut for MyProxied {
type MutProxied = Self;
fn as_mut(&mut self) -> MyProxiedMut<'_> {
self.as_mut()
}
}
#[derive(Debug, Clone, Copy)]
struct MyProxiedView<'msg> {
my_proxied_ref: &'msg MyProxied,
}
impl<'msg> SealedInternal for MyProxiedView<'msg> {}
impl MyProxiedView<'_> {
fn val(&self) -> &str {
&self.my_proxied_ref.val
}
}
impl<'msg> AsView for MyProxiedView<'msg> {
type Proxied = MyProxied;
fn as_view(&self) -> MyProxiedView<'msg> {
*self
}
}
impl<'msg> IntoView<'msg> for MyProxiedView<'msg> {
fn into_view<'shorter>(self) -> MyProxiedView<'shorter>
where
'msg: 'shorter,
{
self
}
}
#[derive(Debug)]
struct MyProxiedMut<'msg> {
my_proxied_ref: &'msg mut MyProxied,
}
impl<'msg> SealedInternal for MyProxiedMut<'msg> {}
impl<'msg> AsView for MyProxiedMut<'msg> {
type Proxied = MyProxied;
fn as_view(&self) -> MyProxiedView<'_> {
MyProxiedView { my_proxied_ref: self.my_proxied_ref }
}
}
impl<'msg> IntoView<'msg> for MyProxiedMut<'msg> {
fn into_view<'shorter>(self) -> View<'shorter, MyProxied>
where
'msg: 'shorter,
{
MyProxiedView { my_proxied_ref: self.my_proxied_ref }
}
}
impl<'msg> AsMut for MyProxiedMut<'msg> {
type MutProxied = MyProxied;
fn as_mut(&mut self) -> MyProxiedMut<'_> {
MyProxiedMut { my_proxied_ref: self.my_proxied_ref }
}
}
impl<'msg> IntoMut<'msg> for MyProxiedMut<'msg> {
fn into_mut<'shorter>(self) -> MyProxiedMut<'shorter>
where
'msg: 'shorter,
{
self
}
}
#[gtest]
fn test_as_view() {
let my_proxied = MyProxied { val: "Hello World".to_string() };
let my_view = my_proxied.as_view();
assert_that!(my_view.val(), eq(&my_proxied.val));
}
fn reborrow_mut_into_view<'msg>(x: Mut<'msg, MyProxied>) -> View<'msg, MyProxied> {
// x.as_view() fails to compile with:
// `ERROR: attempt to return function-local borrowed content`
x.into_view() // OK: we return the same lifetime as we got in.
}
#[gtest]
fn test_mut_into_view() {
let mut my_proxied = MyProxied { val: "Hello World".to_string() };
reborrow_mut_into_view(my_proxied.as_mut());
}
fn require_unified_lifetimes<'msg>(_x: Mut<'msg, MyProxied>, _y: View<'msg, MyProxied>) {}
#[gtest]
fn test_require_unified_lifetimes() {
let mut my_proxied = MyProxied { val: "Hello1".to_string() };
let my_mut = my_proxied.as_mut();
{
let other_proxied = MyProxied { val: "Hello2".to_string() };
let other_view = other_proxied.as_view();
require_unified_lifetimes(my_mut, other_view);
}
}
fn reborrow_generic_as_view<'a, 'b, T>(
x: &'b mut Mut<'a, T>,
y: &'b View<'a, T>,
) -> [View<'b, T>; 2]
where
T: MutProtoObject,
'a: 'b,
{
// `[x, y]` fails to compile because `'a` is not the same as `'b` and the `View`
// lifetime parameter is (conservatively) invariant.
[x.as_view(), y.as_view()]
}
#[gtest]
fn test_reborrow_generic_as_view() {
let mut my_proxied = MyProxied { val: "Hello1".to_string() };
let mut my_mut = my_proxied.as_mut();
let my_ref = &mut my_mut;
{
let other_proxied = MyProxied { val: "Hello2".to_string() };
let other_view = other_proxied.as_view();
reborrow_generic_as_view::<MyProxied>(my_ref, &other_view);
}
}
fn reborrow_generic_view_into_view<'a, 'b, T>(
x: View<'a, T>,
y: View<'b, T>,
) -> [View<'b, T>; 2]
where
T: ProtoObject,
'a: 'b,
{
// `[x, y]` fails to compile because `'a` is not the same as `'b` and the `View`
// lifetime parameter is (conservatively) invariant.
// `[x.as_view(), y]` fails because that borrow cannot outlive `'b`.
[x.into_view(), y]
}
#[gtest]
fn test_reborrow_generic_into_view() {
let my_proxied = MyProxied { val: "Hello1".to_string() };
let my_view = my_proxied.as_view();
{
let other_proxied = MyProxied { val: "Hello2".to_string() };
let other_view = other_proxied.as_view();
reborrow_generic_view_into_view::<MyProxied>(my_view, other_view);
}
}
fn reborrow_generic_mut_into_view<'a, 'b, T>(x: Mut<'a, T>, y: View<'b, T>) -> [View<'b, T>; 2]
where
T: MutProtoObject,
'a: 'b,
{
[x.into_view(), y]
}
#[gtest]
fn test_reborrow_generic_mut_into_view() {
let mut my_proxied = MyProxied { val: "Hello1".to_string() };
let my_mut = my_proxied.as_mut();
{
let other_proxied = MyProxied { val: "Hello2".to_string() };
let other_view = other_proxied.as_view();
reborrow_generic_mut_into_view::<MyProxied>(my_mut, other_view);
}
}
fn reborrow_generic_mut_into_mut<'a, 'b, T>(x: Mut<'a, T>, y: Mut<'b, T>) -> [Mut<'b, T>; 2]
where
T: MutProtoObject,
'a: 'b,
{
// `[x, y]` fails to compile because `'a` is not the same as `'b` and the `Mut`
// lifetime parameter is (conservatively) invariant.
// `[x.as_mut(), y]` fails because that borrow cannot outlive `'b`.
let tmp: Mut<'b, T> = x.into_mut();
[tmp, y]
}
#[gtest]
fn test_reborrow_generic_mut_into_mut() {
let mut my_proxied = MyProxied { val: "Hello1".to_string() };
let my_mut = my_proxied.as_mut();
{
let mut other_proxied = MyProxied { val: "Hello2".to_string() };
let other_mut = other_proxied.as_mut();
// No need to reborrow, even though lifetime of &other_view is different
// than the lifetiem of my_ref. Rust references are covariant over their
// lifetime.
reborrow_generic_mut_into_mut::<MyProxied>(my_mut, other_mut);
}
}
// Functions written the way the `#[diagnostic::on_unimplemented]` notes suggest: accepting a
// generic proxy bound rather than a concrete view or mut proxy.
fn accepts_as_view(_: impl AsView<Proxied = MyProxied>) {}
fn accepts_as_mut(_: impl AsMut<MutProxied = MyProxied>) {}
fn accepts_into_view<'a>(_: impl IntoView<'a, Proxied = MyProxied>) {}
fn accepts_into_mut<'a>(_: impl IntoMut<'a, MutProxied = MyProxied>) {}
#[gtest]
fn test_suggested_conversions_satisfy_proxy_bounds() {
let mut my_proxied = MyProxied { val: "Hello".to_string() };
// Callers of an `impl AsView` / `impl AsMut` function don't need to write `.as_view()` or
// `.as_mut()` at all; a plain borrow suffices.
accepts_as_view(&my_proxied);
accepts_as_mut(&mut my_proxied);
// Explicitly converting, as the notes suggest at the call site, also satisfies the bounds.
accepts_as_view(my_proxied.as_view());
accepts_as_mut(my_proxied.as_mut());
// A view is not itself `AsMut`, but reborrowing a mut proxy is.
let mut my_mut = my_proxied.as_mut();
accepts_as_view(my_mut.as_view());
accepts_as_mut(my_mut.as_mut());
// The `IntoView` / `IntoMut` bounds additionally accept a borrow directly, which is what
// rustc suggests when an owned message is passed to them.
accepts_into_view(&my_proxied);
accepts_into_mut(&mut my_proxied);
accepts_into_view(my_proxied.as_view());
accepts_into_mut(my_proxied.as_mut());
}
}