- We introduce two new view types ProtoStringCow and ProtoBytesCow. - In UPB, for cord field accessors we always return a Cow::Borrowed. - In C++, for coed field accessors we check if the underlying absl::Cord is flat (contigous) and if so return a Cow::Borrowed. If it's not flat we copy the data to a ProtoString and return a Cow::Owned. - We expect the absl::Cord to be flat almost all the time. We have experimentally verified that for small strings (<4 KiB) and less than 6 appends the cord is in fact flat [1]. - This change lifts the requirement of all ViewProxy types to be Copy. Our Cow types cannot be Copy because the owned types aren't copy. [1] https://source.corp.google.com/piper///depot/google3/experimental/users/buchgr/cords/cords.cc PiperOrigin-RevId: 655485943pull/17574/head
parent
cdb723815b
commit
8cdc700b5b
13 changed files with 536 additions and 11 deletions
@ -0,0 +1,117 @@ |
|||||||
|
// Protocol Buffers - Google's data interchange format
|
||||||
|
// Copyright 2024 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
|
||||||
|
|
||||||
|
use crate::__internal::Private; |
||||||
|
use crate::{ |
||||||
|
AsView, IntoProxied, IntoView, ProtoBytes, ProtoStr, ProtoString, Proxied, Proxy, View, |
||||||
|
ViewProxy, |
||||||
|
}; |
||||||
|
use paste::paste; |
||||||
|
use std::cmp::PartialEq; |
||||||
|
use std::ops::Deref; |
||||||
|
|
||||||
|
macro_rules! impl_cord_types { |
||||||
|
($($t:ty, $vt:ty);*) => { |
||||||
|
paste! { $( |
||||||
|
#[derive(Debug)] |
||||||
|
pub struct [< $t Cord>]; |
||||||
|
|
||||||
|
#[derive(Debug)] |
||||||
|
pub enum [< $t Cow>]<'a> { |
||||||
|
Borrowed(View<'a, $t>), |
||||||
|
Owned($t), |
||||||
|
} |
||||||
|
|
||||||
|
impl Proxied for [< $t Cord>] { |
||||||
|
type View<'msg> = [< $t Cow>]<'msg>; |
||||||
|
} |
||||||
|
|
||||||
|
impl<'msg> Proxy<'msg> for [< $t Cow>]<'msg> {} |
||||||
|
|
||||||
|
impl<'msg> ViewProxy<'msg> for [< $t Cow>]<'msg> {} |
||||||
|
|
||||||
|
impl<'msg> AsView for [< $t Cow>]<'msg> { |
||||||
|
type Proxied = [< $t Cord>]; |
||||||
|
|
||||||
|
fn as_view(&self) -> [< $t Cow>]<'_> { |
||||||
|
match self { |
||||||
|
[< $t Cow>]::Owned(owned) => [< $t Cow>]::Borrowed((*owned).as_view()), |
||||||
|
[< $t Cow>]::Borrowed(borrowed) => [< $t Cow>]::Borrowed(borrowed), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'msg> IntoView<'msg> for [< $t Cow>]<'msg> { |
||||||
|
fn into_view<'shorter>(self) -> [< $t Cow>]<'shorter> |
||||||
|
where |
||||||
|
'msg: 'shorter, { |
||||||
|
match self { |
||||||
|
[< $t Cow>]::Owned(owned) => [< $t Cow>]::Owned(owned), |
||||||
|
[< $t Cow>]::Borrowed(borrow) => [< $t Cow>]::Borrowed(borrow.into_view()), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl IntoProxied<$t> for [< $t Cow>]<'_> { |
||||||
|
fn into_proxied(self, _private: Private) -> $t { |
||||||
|
match self { |
||||||
|
[< $t Cow>]::Owned(owned) => owned, |
||||||
|
[< $t Cow>]::Borrowed(borrowed) => borrowed.into_proxied(Private), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> Deref for [< $t Cow>]<'a> { |
||||||
|
type Target = $vt; |
||||||
|
|
||||||
|
fn deref(&self) -> View<'_, $t> { |
||||||
|
match self { |
||||||
|
[< $t Cow>]::Borrowed(borrow) => borrow, |
||||||
|
[< $t Cow>]::Owned(owned) => (*owned).as_view(), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl AsRef<[u8]> for [< $t Cow>]<'_> { |
||||||
|
fn as_ref(&self) -> &[u8] { |
||||||
|
match self { |
||||||
|
[< $t Cow>]::Borrowed(borrow) => borrow.as_ref(), |
||||||
|
[< $t Cow>]::Owned(owned) => owned.as_ref(), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
)* |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl_cord_types!( |
||||||
|
ProtoString, ProtoStr; |
||||||
|
ProtoBytes, [u8] |
||||||
|
); |
||||||
|
|
||||||
|
macro_rules! impl_eq { |
||||||
|
($($t1:ty, $t2:ty);*) => { |
||||||
|
paste! { $( |
||||||
|
impl PartialEq<$t1> for $t2 { |
||||||
|
fn eq(&self, rhs: &$t1) -> bool { |
||||||
|
AsRef::<[u8]>::as_ref(self) == AsRef::<[u8]>::as_ref(rhs) |
||||||
|
} |
||||||
|
} |
||||||
|
)* |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl_eq!( |
||||||
|
ProtoStringCow<'_>, ProtoStringCow<'_>; |
||||||
|
str, ProtoStringCow<'_>; |
||||||
|
ProtoStringCow<'_>, str; |
||||||
|
ProtoBytesCow<'_>, ProtoBytesCow<'_>; |
||||||
|
[u8], ProtoBytesCow<'_>; |
||||||
|
ProtoBytesCow<'_>, [u8] |
||||||
|
); |
@ -0,0 +1,38 @@ |
|||||||
|
// Protocol Buffers - Google's data interchange format
|
||||||
|
// Copyright 2024 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
|
||||||
|
|
||||||
|
use googletest::prelude::*; |
||||||
|
|
||||||
|
use unittest_rust_proto::{TestAllTypes, TestCord}; |
||||||
|
|
||||||
|
#[googletest::test] |
||||||
|
fn test_string_cord() { |
||||||
|
let mut msg = TestAllTypes::new(); |
||||||
|
assert_that!(msg.has_optional_cord(), eq(false)); |
||||||
|
assert_that!(msg.optional_cord(), eq("")); |
||||||
|
msg.set_optional_cord("hello"); |
||||||
|
assert_that!(msg.has_optional_cord(), eq(true)); |
||||||
|
assert_that!(msg.optional_cord(), eq("hello")); |
||||||
|
|
||||||
|
let mut msg2 = TestAllTypes::new(); |
||||||
|
msg2.set_optional_cord(msg.optional_cord()); |
||||||
|
assert_that!(msg2.optional_cord(), eq("hello")); |
||||||
|
} |
||||||
|
|
||||||
|
#[googletest::test] |
||||||
|
fn test_bytes_cord() { |
||||||
|
let mut msg = TestCord::new(); |
||||||
|
assert_that!(msg.has_optional_bytes_cord(), eq(false)); |
||||||
|
assert_that!(msg.optional_bytes_cord(), eq("".as_bytes())); |
||||||
|
msg.set_optional_bytes_cord(b"hello"); |
||||||
|
assert_that!(msg.has_optional_bytes_cord(), eq(true)); |
||||||
|
assert_that!(msg.optional_bytes_cord(), eq("hello".as_bytes())); |
||||||
|
|
||||||
|
let mut msg2 = TestCord::new(); |
||||||
|
msg2.set_optional_bytes_cord(msg.optional_bytes_cord()); |
||||||
|
assert_that!(msg2.optional_bytes_cord(), eq("hello".as_bytes())); |
||||||
|
} |
@ -0,0 +1,288 @@ |
|||||||
|
// Protocol Buffers - Google's data interchange format
|
||||||
|
// Copyright 2024 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
|
||||||
|
|
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "absl/strings/string_view.h" |
||||||
|
#include "google/protobuf/compiler/cpp/helpers.h" |
||||||
|
#include "google/protobuf/compiler/rust/accessors/accessor_case.h" |
||||||
|
#include "google/protobuf/compiler/rust/accessors/generator.h" |
||||||
|
#include "google/protobuf/compiler/rust/context.h" |
||||||
|
#include "google/protobuf/compiler/rust/naming.h" |
||||||
|
#include "google/protobuf/descriptor.h" |
||||||
|
|
||||||
|
namespace google { |
||||||
|
namespace protobuf { |
||||||
|
namespace compiler { |
||||||
|
namespace rust { |
||||||
|
|
||||||
|
void SingularCord::InMsgImpl(Context& ctx, const FieldDescriptor& field, |
||||||
|
AccessorCase accessor_case) const { |
||||||
|
std::string field_name = FieldNameWithCollisionAvoidance(field); |
||||||
|
bool is_string_type = field.type() == FieldDescriptor::TYPE_STRING; |
||||||
|
ctx.Emit( |
||||||
|
{{"field", RsSafeName(field_name)}, |
||||||
|
{"raw_field_name", field_name}, |
||||||
|
{"hazzer_thunk", ThunkName(ctx, field, "has")}, |
||||||
|
{"borrowed_getter_thunk", ThunkName(ctx, field, "get_cord_borrowed")}, |
||||||
|
{"owned_getter_thunk", ThunkName(ctx, field, "get_cord_owned")}, |
||||||
|
{"is_flat_thunk", ThunkName(ctx, field, "cord_is_flat")}, |
||||||
|
{"setter_thunk", ThunkName(ctx, field, "set")}, |
||||||
|
{"clearer_thunk", ThunkName(ctx, field, "clear")}, |
||||||
|
{"getter_thunk", ThunkName(ctx, field, "get")}, |
||||||
|
{"proxied_type", RsTypePath(ctx, field)}, |
||||||
|
{"borrowed_type", |
||||||
|
[&] { |
||||||
|
if (is_string_type) { |
||||||
|
ctx.Emit("$pb$::ProtoStr"); |
||||||
|
} else { |
||||||
|
ctx.Emit("[u8]"); |
||||||
|
} |
||||||
|
}}, |
||||||
|
{"transform_borrowed", |
||||||
|
[&] { |
||||||
|
if (is_string_type) { |
||||||
|
ctx.Emit(R"rs( |
||||||
|
$pb$::ProtoStringCow::Borrowed( |
||||||
|
// SAFETY: The runtime doesn't require ProtoStr to be UTF-8.
|
||||||
|
unsafe { $pb$::ProtoStr::from_utf8_unchecked(view) } |
||||||
|
) |
||||||
|
)rs"); |
||||||
|
} else { |
||||||
|
ctx.Emit(R"rs( |
||||||
|
$pb$::ProtoBytesCow::Borrowed( |
||||||
|
view |
||||||
|
) |
||||||
|
)rs"); |
||||||
|
} |
||||||
|
}}, |
||||||
|
{"transform_owned", |
||||||
|
[&] { |
||||||
|
if (is_string_type) { |
||||||
|
ctx.Emit(R"rs( |
||||||
|
$pb$::ProtoStringCow::Owned( |
||||||
|
$pb$::ProtoString::from_inner($pbi$::Private, inner) |
||||||
|
) |
||||||
|
)rs"); |
||||||
|
} else { |
||||||
|
ctx.Emit(R"rs( |
||||||
|
$pb$::ProtoBytesCow::Owned( |
||||||
|
$pb$::ProtoBytes::from_inner($pbi$::Private, inner) |
||||||
|
) |
||||||
|
)rs"); |
||||||
|
} |
||||||
|
}}, |
||||||
|
{"view_lifetime", ViewLifetime(accessor_case)}, |
||||||
|
{"view_type", |
||||||
|
[&] { |
||||||
|
if (is_string_type) { |
||||||
|
ctx.Emit("$pb$::ProtoStringCow<$view_lifetime$>"); |
||||||
|
} else { |
||||||
|
ctx.Emit("$pb$::ProtoBytesCow<$view_lifetime$>"); |
||||||
|
} |
||||||
|
}}, |
||||||
|
{"view_self", ViewReceiver(accessor_case)}, |
||||||
|
{"getter_impl", |
||||||
|
[&] { |
||||||
|
if (ctx.is_cpp()) { |
||||||
|
ctx.Emit(R"rs( |
||||||
|
let cord_is_flat = unsafe { $is_flat_thunk$(self.raw_msg()) }; |
||||||
|
if cord_is_flat { |
||||||
|
let view = unsafe { $borrowed_getter_thunk$(self.raw_msg()).as_ref() }; |
||||||
|
return $transform_borrowed$; |
||||||
|
} |
||||||
|
|
||||||
|
let owned = unsafe { $owned_getter_thunk$(self.raw_msg()) }; |
||||||
|
let inner = unsafe { $pbr$::InnerProtoString::from_raw($pbi$::Private, owned) }; |
||||||
|
|
||||||
|
$transform_owned$ |
||||||
|
)rs"); |
||||||
|
} else { |
||||||
|
ctx.Emit(R"rs( |
||||||
|
let view = unsafe { $getter_thunk$(self.raw_msg()).as_ref() }; |
||||||
|
$transform_borrowed$ |
||||||
|
)rs"); |
||||||
|
} |
||||||
|
}}, |
||||||
|
{"getter", |
||||||
|
[&] { |
||||||
|
ctx.Emit(R"rs( |
||||||
|
pub fn $field$($view_self$) -> $view_type$ { |
||||||
|
$getter_impl$ |
||||||
|
} |
||||||
|
)rs"); |
||||||
|
}}, |
||||||
|
{"setter_impl", |
||||||
|
[&] { |
||||||
|
if (ctx.is_cpp()) { |
||||||
|
ctx.Emit({}, |
||||||
|
R"rs( |
||||||
|
let s = val.into_proxied($pbi$::Private); |
||||||
|
unsafe { |
||||||
|
$setter_thunk$( |
||||||
|
self.as_mutator_message_ref($pbi$::Private).msg(), |
||||||
|
s.into_inner($pbi$::Private).into_raw($pbi$::Private) |
||||||
|
); |
||||||
|
} |
||||||
|
)rs"); |
||||||
|
} else { |
||||||
|
ctx.Emit(R"rs( |
||||||
|
let s = val.into_proxied($pbi$::Private); |
||||||
|
let (view, arena) = |
||||||
|
s.into_inner($pbi$::Private).into_raw_parts($pbi$::Private); |
||||||
|
|
||||||
|
let mm_ref = |
||||||
|
self.as_mutator_message_ref($pbi$::Private); |
||||||
|
let parent_arena = mm_ref.arena($pbi$::Private); |
||||||
|
|
||||||
|
parent_arena.fuse(&arena); |
||||||
|
|
||||||
|
unsafe { |
||||||
|
$setter_thunk$( |
||||||
|
self.as_mutator_message_ref($pbi$::Private).msg(), |
||||||
|
view |
||||||
|
); |
||||||
|
} |
||||||
|
)rs"); |
||||||
|
} |
||||||
|
}}, |
||||||
|
{"setter", |
||||||
|
[&] { |
||||||
|
if (accessor_case == AccessorCase::VIEW) return; |
||||||
|
ctx.Emit({}, |
||||||
|
R"rs( |
||||||
|
pub fn set_$raw_field_name$(&mut self, val: impl $pb$::IntoProxied<$proxied_type$>) { |
||||||
|
$setter_impl$ |
||||||
|
} |
||||||
|
)rs"); |
||||||
|
}}, |
||||||
|
{"hazzer", |
||||||
|
[&] { |
||||||
|
if (!field.has_presence()) return; |
||||||
|
ctx.Emit({}, R"rs( |
||||||
|
pub fn has_$raw_field_name$($view_self$) -> bool { |
||||||
|
unsafe { $hazzer_thunk$(self.raw_msg()) } |
||||||
|
})rs"); |
||||||
|
}}, |
||||||
|
{"clearer", |
||||||
|
[&] { |
||||||
|
if (accessor_case == AccessorCase::VIEW) return; |
||||||
|
if (!field.has_presence()) return; |
||||||
|
ctx.Emit({}, R"rs( |
||||||
|
pub fn clear_$raw_field_name$(&mut self) { |
||||||
|
unsafe { $clearer_thunk$(self.raw_msg()) } |
||||||
|
})rs"); |
||||||
|
}}}, |
||||||
|
R"rs( |
||||||
|
$getter$ |
||||||
|
$setter$ |
||||||
|
$hazzer$ |
||||||
|
$clearer$ |
||||||
|
)rs"); |
||||||
|
} |
||||||
|
|
||||||
|
void SingularCord::InExternC(Context& ctx, const FieldDescriptor& field) const { |
||||||
|
ctx.Emit( |
||||||
|
{{"hazzer_thunk", ThunkName(ctx, field, "has")}, |
||||||
|
{"borrowed_getter_thunk", ThunkName(ctx, field, "get_cord_borrowed")}, |
||||||
|
{"owned_getter_thunk", ThunkName(ctx, field, "get_cord_owned")}, |
||||||
|
{"is_flat_thunk", ThunkName(ctx, field, "cord_is_flat")}, |
||||||
|
{"getter_thunk", ThunkName(ctx, field, "get")}, |
||||||
|
{"setter_thunk", ThunkName(ctx, field, "set")}, |
||||||
|
{"setter", |
||||||
|
[&] { |
||||||
|
if (ctx.is_cpp()) { |
||||||
|
ctx.Emit(R"rs( |
||||||
|
fn $setter_thunk$(raw_msg: $pbr$::RawMessage, val: $pbr$::CppStdString); |
||||||
|
)rs"); |
||||||
|
} else { |
||||||
|
ctx.Emit(R"rs( |
||||||
|
fn $setter_thunk$(raw_msg: $pbr$::RawMessage, val: $pbr$::PtrAndLen); |
||||||
|
)rs"); |
||||||
|
} |
||||||
|
}}, |
||||||
|
{"clearer_thunk", ThunkName(ctx, field, "clear")}, |
||||||
|
{"getter_thunks", |
||||||
|
[&] { |
||||||
|
if (ctx.is_cpp()) { |
||||||
|
ctx.Emit(R"rs( |
||||||
|
fn $is_flat_thunk$(raw_msg: $pbr$::RawMessage) -> bool; |
||||||
|
fn $borrowed_getter_thunk$(raw_msg: $pbr$::RawMessage) -> $pbr$::PtrAndLen; |
||||||
|
fn $owned_getter_thunk$(raw_msg: $pbr$::RawMessage) -> $pbr$::CppStdString; |
||||||
|
)rs"); |
||||||
|
} else { |
||||||
|
ctx.Emit(R"rs( |
||||||
|
fn $getter_thunk$(raw_msg: $pbr$::RawMessage) -> $pbr$::PtrAndLen; |
||||||
|
)rs"); |
||||||
|
} |
||||||
|
}}, |
||||||
|
{"with_presence_fields_thunks", |
||||||
|
[&] { |
||||||
|
if (field.has_presence()) { |
||||||
|
ctx.Emit(R"rs( |
||||||
|
fn $hazzer_thunk$(raw_msg: $pbr$::RawMessage) -> bool; |
||||||
|
fn $clearer_thunk$(raw_msg: $pbr$::RawMessage); |
||||||
|
)rs"); |
||||||
|
} |
||||||
|
}}}, |
||||||
|
R"rs( |
||||||
|
$with_presence_fields_thunks$ |
||||||
|
$getter_thunks$ |
||||||
|
$setter$ |
||||||
|
)rs"); |
||||||
|
} |
||||||
|
|
||||||
|
void SingularCord::InThunkCc(Context& ctx, const FieldDescriptor& field) const { |
||||||
|
ctx.Emit( |
||||||
|
{{"field", cpp::FieldName(&field)}, |
||||||
|
{"QualifiedMsg", cpp::QualifiedClassName(field.containing_type())}, |
||||||
|
{"hazzer_thunk", ThunkName(ctx, field, "has")}, |
||||||
|
{"setter_thunk", ThunkName(ctx, field, "set")}, |
||||||
|
{"clearer_thunk", ThunkName(ctx, field, "clear")}, |
||||||
|
{"borrowed_getter_thunk", ThunkName(ctx, field, "get_cord_borrowed")}, |
||||||
|
{"owned_getter_thunk", ThunkName(ctx, field, "get_cord_owned")}, |
||||||
|
{"is_flat_thunk", ThunkName(ctx, field, "cord_is_flat")}, |
||||||
|
{"with_presence_fields_thunks", |
||||||
|
[&] { |
||||||
|
if (field.has_presence()) { |
||||||
|
ctx.Emit(R"cc( |
||||||
|
bool $hazzer_thunk$($QualifiedMsg$* msg) { |
||||||
|
return msg->has_$field$(); |
||||||
|
} |
||||||
|
void $clearer_thunk$($QualifiedMsg$* msg) { msg->clear_$field$(); } |
||||||
|
)cc"); |
||||||
|
} |
||||||
|
}}}, |
||||||
|
R"cc( |
||||||
|
$with_presence_fields_thunks$; |
||||||
|
|
||||||
|
bool $is_flat_thunk$($QualifiedMsg$* msg) { |
||||||
|
const absl::Cord& cord = msg->$field$(); |
||||||
|
return cord.TryFlat().has_value(); |
||||||
|
} |
||||||
|
::google::protobuf::rust::PtrAndLen $borrowed_getter_thunk$($QualifiedMsg$* msg) { |
||||||
|
const absl::Cord& cord = msg->$field$(); |
||||||
|
absl::string_view s = cord.TryFlat().value(); |
||||||
|
return ::google::protobuf::rust::PtrAndLen(s.data(), s.size()); |
||||||
|
} |
||||||
|
std::string* $owned_getter_thunk$($QualifiedMsg$* msg) { |
||||||
|
const absl::Cord& cord = msg->$field$(); |
||||||
|
std::string* owned = new std::string(); |
||||||
|
absl::CopyCordToString(cord, owned); |
||||||
|
return owned; |
||||||
|
} |
||||||
|
void $setter_thunk$($QualifiedMsg$* msg, std::string* s) { |
||||||
|
msg->set_$field$(absl::Cord(std::move(*s))); |
||||||
|
delete s; |
||||||
|
} |
||||||
|
)cc"); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace rust
|
||||||
|
} // namespace compiler
|
||||||
|
} // namespace protobuf
|
||||||
|
} // namespace google
|
Loading…
Reference in new issue