Implement Reflect for tuples up to length 12 (#1218)
Add Reflect impls for tuples up to length 12
This commit is contained in:
parent
9bce8712b5
commit
5e7456115a
@ -3,6 +3,7 @@ mod map;
|
||||
mod path;
|
||||
mod reflect;
|
||||
mod struct_trait;
|
||||
mod tuple;
|
||||
mod tuple_struct;
|
||||
mod type_registry;
|
||||
mod type_uuid;
|
||||
@ -46,6 +47,7 @@ pub use map::*;
|
||||
pub use path::*;
|
||||
pub use reflect::*;
|
||||
pub use struct_trait::*;
|
||||
pub use tuple::*;
|
||||
pub use tuple_struct::*;
|
||||
pub use type_registry::*;
|
||||
pub use type_uuid::*;
|
||||
@ -208,6 +210,7 @@ mod tests {
|
||||
c: Vec<isize>,
|
||||
d: HashMap<usize, i8>,
|
||||
e: Bar,
|
||||
f: (i32, Vec<isize>, Bar),
|
||||
}
|
||||
|
||||
#[derive(Reflect, Eq, PartialEq, Debug)]
|
||||
@ -224,6 +227,7 @@ mod tests {
|
||||
c: vec![1, 2],
|
||||
d: hash_map,
|
||||
e: Bar { x: 1 },
|
||||
f: (1, vec![1, 2], Bar { x: 1 }),
|
||||
};
|
||||
|
||||
let mut foo_patch = DynamicStruct::default();
|
||||
@ -234,7 +238,7 @@ mod tests {
|
||||
list.push(3isize);
|
||||
list.push(4isize);
|
||||
list.push(5isize);
|
||||
foo_patch.insert("c", list);
|
||||
foo_patch.insert("c", list.clone_dynamic());
|
||||
|
||||
let mut map = DynamicMap::default();
|
||||
map.insert(2usize, 3i8);
|
||||
@ -242,7 +246,13 @@ mod tests {
|
||||
|
||||
let mut bar_patch = DynamicStruct::default();
|
||||
bar_patch.insert("x", 2u32);
|
||||
foo_patch.insert("e", bar_patch);
|
||||
foo_patch.insert("e", bar_patch.clone_dynamic());
|
||||
|
||||
let mut tuple = DynamicTuple::default();
|
||||
tuple.insert(2i32);
|
||||
tuple.insert(list);
|
||||
tuple.insert(bar_patch);
|
||||
foo_patch.insert("f", tuple);
|
||||
|
||||
foo.apply(&foo_patch);
|
||||
|
||||
@ -255,6 +265,7 @@ mod tests {
|
||||
c: vec![3, 4, 5],
|
||||
d: hash_map,
|
||||
e: Bar { x: 2 },
|
||||
f: (2, vec![3, 4, 5], Bar { x: 2 }),
|
||||
};
|
||||
|
||||
assert_eq!(foo, expected_foo);
|
||||
@ -271,6 +282,7 @@ mod tests {
|
||||
d: HashMap<usize, i8>,
|
||||
e: Bar,
|
||||
f: String,
|
||||
g: (i32, Vec<isize>, Bar),
|
||||
}
|
||||
|
||||
#[derive(Reflect)]
|
||||
@ -288,6 +300,7 @@ mod tests {
|
||||
d: hash_map,
|
||||
e: Bar { x: 1 },
|
||||
f: "hi".to_string(),
|
||||
g: (1, vec![1, 2], Bar { x: 1 }),
|
||||
};
|
||||
|
||||
let mut registry = TypeRegistry::default();
|
||||
@ -297,6 +310,7 @@ mod tests {
|
||||
registry.register::<Bar>();
|
||||
registry.register::<String>();
|
||||
registry.register::<i8>();
|
||||
registry.register::<i32>();
|
||||
|
||||
let serializer = ReflectSerializer::new(&foo, ®istry);
|
||||
let serialized = to_string_pretty(&serializer, PrettyConfig::default()).unwrap();
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::{serde::Serializable, List, Map, Struct, TupleStruct};
|
||||
use crate::{serde::Serializable, List, Map, Struct, Tuple, TupleStruct};
|
||||
use std::{any::Any, fmt::Debug};
|
||||
|
||||
pub use bevy_utils::AHasher as ReflectHasher;
|
||||
@ -6,6 +6,7 @@ pub use bevy_utils::AHasher as ReflectHasher;
|
||||
pub enum ReflectRef<'a> {
|
||||
Struct(&'a dyn Struct),
|
||||
TupleStruct(&'a dyn TupleStruct),
|
||||
Tuple(&'a dyn Tuple),
|
||||
List(&'a dyn List),
|
||||
Map(&'a dyn Map),
|
||||
Value(&'a dyn Reflect),
|
||||
@ -14,6 +15,7 @@ pub enum ReflectRef<'a> {
|
||||
pub enum ReflectMut<'a> {
|
||||
Struct(&'a mut dyn Struct),
|
||||
TupleStruct(&'a mut dyn TupleStruct),
|
||||
Tuple(&'a dyn Tuple),
|
||||
List(&'a mut dyn List),
|
||||
Map(&'a mut dyn Map),
|
||||
Value(&'a mut dyn Reflect),
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
serde::type_fields, DynamicList, DynamicMap, DynamicStruct, DynamicTupleStruct, Reflect,
|
||||
ReflectDeserialize, TypeRegistry,
|
||||
serde::type_fields, DynamicList, DynamicMap, DynamicStruct, DynamicTuple, DynamicTupleStruct,
|
||||
Reflect, ReflectDeserialize, TypeRegistry,
|
||||
};
|
||||
use erased_serde::Deserializer;
|
||||
use serde::de::{self, DeserializeSeed, MapAccess, SeqAccess, Visitor};
|
||||
@ -176,6 +176,15 @@ impl<'a, 'de> Visitor<'de> for ReflectVisitor<'a> {
|
||||
tuple_struct.set_name(type_name);
|
||||
return Ok(Box::new(tuple_struct));
|
||||
}
|
||||
type_fields::TUPLE => {
|
||||
let _type_name = type_name
|
||||
.take()
|
||||
.ok_or_else(|| de::Error::missing_field(type_fields::TYPE))?;
|
||||
let tuple = map.next_value_seed(TupleDeserializer {
|
||||
registry: self.registry,
|
||||
})?;
|
||||
return Ok(Box::new(tuple));
|
||||
}
|
||||
type_fields::LIST => {
|
||||
let _type_name = type_name
|
||||
.take()
|
||||
@ -401,3 +410,45 @@ impl<'a, 'de> Visitor<'de> for TupleStructVisitor<'a> {
|
||||
Ok(tuple_struct)
|
||||
}
|
||||
}
|
||||
|
||||
struct TupleDeserializer<'a> {
|
||||
registry: &'a TypeRegistry,
|
||||
}
|
||||
|
||||
impl<'a, 'de> DeserializeSeed<'de> for TupleDeserializer<'a> {
|
||||
type Value = DynamicTuple;
|
||||
|
||||
fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_seq(TupleVisitor {
|
||||
registry: self.registry,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct TupleVisitor<'a> {
|
||||
registry: &'a TypeRegistry,
|
||||
}
|
||||
|
||||
impl<'a, 'de> Visitor<'de> for TupleVisitor<'a> {
|
||||
type Value = DynamicTuple;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("tuple value")
|
||||
}
|
||||
|
||||
fn visit_seq<V>(self, mut seq: V) -> Result<Self::Value, V::Error>
|
||||
where
|
||||
V: SeqAccess<'de>,
|
||||
{
|
||||
let mut tuple = DynamicTuple::default();
|
||||
while let Some(value) = seq.next_element_seed(ReflectDeserializer {
|
||||
registry: self.registry,
|
||||
})? {
|
||||
tuple.insert_boxed(value);
|
||||
}
|
||||
Ok(tuple)
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ pub(crate) mod type_fields {
|
||||
pub const MAP: &str = "map";
|
||||
pub const STRUCT: &str = "struct";
|
||||
pub const TUPLE_STRUCT: &str = "tuple_struct";
|
||||
pub const TUPLE: &str = "tuple";
|
||||
pub const LIST: &str = "list";
|
||||
pub const VALUE: &str = "value";
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
serde::type_fields, List, Map, Reflect, ReflectRef, Struct, TupleStruct, TypeRegistry,
|
||||
serde::type_fields, List, Map, Reflect, ReflectRef, Struct, Tuple, TupleStruct, TypeRegistry,
|
||||
};
|
||||
use serde::{
|
||||
ser::{SerializeMap, SerializeSeq},
|
||||
@ -57,6 +57,11 @@ impl<'a> Serialize for ReflectSerializer<'a> {
|
||||
registry: self.registry,
|
||||
}
|
||||
.serialize(serializer),
|
||||
ReflectRef::Tuple(value) => TupleSerializer {
|
||||
tuple: value,
|
||||
registry: self.registry,
|
||||
}
|
||||
.serialize(serializer),
|
||||
ReflectRef::List(value) => ListSerializer {
|
||||
list: value,
|
||||
registry: self.registry,
|
||||
@ -181,6 +186,48 @@ impl<'a> Serialize for TupleStructValueSerializer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TupleSerializer<'a> {
|
||||
pub tuple: &'a dyn Tuple,
|
||||
pub registry: &'a TypeRegistry,
|
||||
}
|
||||
|
||||
impl<'a> Serialize for TupleSerializer<'a> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut state = serializer.serialize_map(Some(2))?;
|
||||
|
||||
state.serialize_entry(type_fields::TYPE, self.tuple.type_name())?;
|
||||
state.serialize_entry(
|
||||
type_fields::TUPLE,
|
||||
&TupleValueSerializer {
|
||||
tuple: self.tuple,
|
||||
registry: self.registry,
|
||||
},
|
||||
)?;
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TupleValueSerializer<'a> {
|
||||
pub tuple: &'a dyn Tuple,
|
||||
pub registry: &'a TypeRegistry,
|
||||
}
|
||||
|
||||
impl<'a> Serialize for TupleValueSerializer<'a> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut state = serializer.serialize_seq(Some(self.tuple.field_len()))?;
|
||||
for value in self.tuple.iter_fields() {
|
||||
state.serialize_element(&ReflectSerializer::new(value, self.registry))?;
|
||||
}
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MapSerializer<'a> {
|
||||
pub map: &'a dyn Map,
|
||||
pub registry: &'a TypeRegistry,
|
||||
|
||||
310
crates/bevy_reflect/src/tuple.rs
Normal file
310
crates/bevy_reflect/src/tuple.rs
Normal file
@ -0,0 +1,310 @@
|
||||
use std::any::Any;
|
||||
|
||||
use crate::{serde::Serializable, Reflect, ReflectMut, ReflectRef};
|
||||
|
||||
pub trait Tuple: Reflect {
|
||||
fn field(&self, index: usize) -> Option<&dyn Reflect>;
|
||||
fn field_mut(&mut self, index: usize) -> Option<&mut dyn Reflect>;
|
||||
fn field_len(&self) -> usize;
|
||||
fn iter_fields(&self) -> TupleFieldIter;
|
||||
fn clone_dynamic(&self) -> DynamicTuple;
|
||||
}
|
||||
|
||||
pub struct TupleFieldIter<'a> {
|
||||
pub(crate) tuple: &'a dyn Tuple,
|
||||
pub(crate) index: usize,
|
||||
}
|
||||
|
||||
impl<'a> TupleFieldIter<'a> {
|
||||
pub fn new(value: &'a dyn Tuple) -> Self {
|
||||
TupleFieldIter {
|
||||
tuple: value,
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for TupleFieldIter<'a> {
|
||||
type Item = &'a dyn Reflect;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let value = self.tuple.field(self.index);
|
||||
self.index += 1;
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
pub trait GetTupleField {
|
||||
fn get_field<T: Reflect>(&self, index: usize) -> Option<&T>;
|
||||
fn get_field_mut<T: Reflect>(&mut self, index: usize) -> Option<&mut T>;
|
||||
}
|
||||
|
||||
impl<S: Tuple> GetTupleField for S {
|
||||
fn get_field<T: Reflect>(&self, index: usize) -> Option<&T> {
|
||||
self.field(index)
|
||||
.and_then(|value| value.downcast_ref::<T>())
|
||||
}
|
||||
|
||||
fn get_field_mut<T: Reflect>(&mut self, index: usize) -> Option<&mut T> {
|
||||
self.field_mut(index)
|
||||
.and_then(|value| value.downcast_mut::<T>())
|
||||
}
|
||||
}
|
||||
|
||||
impl GetTupleField for dyn Tuple {
|
||||
fn get_field<T: Reflect>(&self, index: usize) -> Option<&T> {
|
||||
self.field(index)
|
||||
.and_then(|value| value.downcast_ref::<T>())
|
||||
}
|
||||
|
||||
fn get_field_mut<T: Reflect>(&mut self, index: usize) -> Option<&mut T> {
|
||||
self.field_mut(index)
|
||||
.and_then(|value| value.downcast_mut::<T>())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DynamicTuple {
|
||||
pub(crate) fields: Vec<Box<dyn Reflect>>,
|
||||
}
|
||||
|
||||
impl DynamicTuple {
|
||||
pub fn insert_boxed(&mut self, value: Box<dyn Reflect>) {
|
||||
self.fields.push(value);
|
||||
}
|
||||
|
||||
pub fn insert<T: Reflect>(&mut self, value: T) {
|
||||
self.insert_boxed(Box::new(value));
|
||||
}
|
||||
}
|
||||
|
||||
impl Tuple for DynamicTuple {
|
||||
#[inline]
|
||||
fn field(&self, index: usize) -> Option<&dyn Reflect> {
|
||||
self.fields.get(index).map(|field| &**field)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn field_mut(&mut self, index: usize) -> Option<&mut dyn Reflect> {
|
||||
self.fields.get_mut(index).map(|field| &mut **field)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn field_len(&self) -> usize {
|
||||
self.fields.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn iter_fields(&self) -> TupleFieldIter {
|
||||
TupleFieldIter {
|
||||
tuple: self,
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clone_dynamic(&self) -> DynamicTuple {
|
||||
DynamicTuple {
|
||||
fields: self
|
||||
.fields
|
||||
.iter()
|
||||
.map(|value| value.clone_value())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Reflect for DynamicTuple {
|
||||
#[inline]
|
||||
fn type_name(&self) -> &str {
|
||||
std::any::type_name::<Self>()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clone_value(&self) -> Box<dyn Reflect> {
|
||||
Box::new(self.clone_dynamic())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn reflect_ref(&self) -> ReflectRef {
|
||||
ReflectRef::Tuple(self)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn reflect_mut(&mut self) -> ReflectMut {
|
||||
ReflectMut::Tuple(self)
|
||||
}
|
||||
|
||||
fn apply(&mut self, value: &dyn Reflect) {
|
||||
tuple_apply(self, value);
|
||||
}
|
||||
|
||||
fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
|
||||
*self = value.take()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reflect_hash(&self) -> Option<u64> {
|
||||
None
|
||||
}
|
||||
|
||||
fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option<bool> {
|
||||
tuple_partial_eq(self, value)
|
||||
}
|
||||
|
||||
fn serializable(&self) -> Option<Serializable> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn tuple_apply<T: Tuple>(a: &mut T, b: &dyn Reflect) {
|
||||
if let ReflectRef::Tuple(tuple) = b.reflect_ref() {
|
||||
for (i, value) in tuple.iter_fields().enumerate() {
|
||||
if let Some(v) = a.field_mut(i) {
|
||||
v.apply(value)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("Attempted to apply non-Tuple type to Tuple type.");
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn tuple_partial_eq<T: Tuple>(a: &T, b: &dyn Reflect) -> Option<bool> {
|
||||
let b = if let ReflectRef::Tuple(tuple) = b.reflect_ref() {
|
||||
tuple
|
||||
} else {
|
||||
return Some(false);
|
||||
};
|
||||
|
||||
if a.field_len() != b.field_len() {
|
||||
return Some(false);
|
||||
}
|
||||
|
||||
for (a_field, b_field) in a.iter_fields().zip(b.iter_fields()) {
|
||||
match a_field.reflect_partial_eq(b_field) {
|
||||
Some(false) | None => return Some(false),
|
||||
Some(true) => {}
|
||||
}
|
||||
}
|
||||
|
||||
Some(true)
|
||||
}
|
||||
|
||||
macro_rules! impl_reflect_tuple {
|
||||
{$($index:tt : $name:tt),*} => {
|
||||
impl<$($name: Reflect),*> Tuple for ($($name,)*) {
|
||||
#[inline]
|
||||
fn field(&self, index: usize) -> Option<&dyn Reflect> {
|
||||
match index {
|
||||
$($index => Some(&self.$index as &dyn Reflect),)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn field_mut(&mut self, index: usize) -> Option<&mut dyn Reflect> {
|
||||
match index {
|
||||
$($index => Some(&mut self.$index as &mut dyn Reflect),)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn field_len(&self) -> usize {
|
||||
let indices: &[usize] = &[$($index as usize),*];
|
||||
indices.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn iter_fields(&self) -> TupleFieldIter {
|
||||
TupleFieldIter {
|
||||
tuple: self,
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clone_dynamic(&self) -> DynamicTuple {
|
||||
DynamicTuple {
|
||||
fields: self
|
||||
.iter_fields()
|
||||
.map(|value| value.clone_value())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<$($name: Reflect),*> Reflect for ($($name,)*) {
|
||||
fn type_name(&self) -> &str {
|
||||
std::any::type_name::<Self>()
|
||||
}
|
||||
|
||||
fn any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn apply(&mut self, value: &dyn Reflect) {
|
||||
crate::tuple_apply(self, value);
|
||||
}
|
||||
|
||||
fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
|
||||
*self = value.take()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reflect_ref(&self) -> ReflectRef {
|
||||
ReflectRef::Tuple(self)
|
||||
}
|
||||
|
||||
fn reflect_mut(&mut self) -> ReflectMut {
|
||||
ReflectMut::Tuple(self)
|
||||
}
|
||||
|
||||
fn clone_value(&self) -> Box<dyn Reflect> {
|
||||
Box::new(self.clone_dynamic())
|
||||
}
|
||||
|
||||
fn reflect_hash(&self) -> Option<u64> {
|
||||
None
|
||||
}
|
||||
|
||||
fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option<bool> {
|
||||
crate::tuple_partial_eq(self, value)
|
||||
}
|
||||
|
||||
fn serializable(&self) -> Option<Serializable> {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_reflect_tuple! {}
|
||||
impl_reflect_tuple! {0: A}
|
||||
impl_reflect_tuple! {0: A, 1: B}
|
||||
impl_reflect_tuple! {0: A, 1: B, 2: C}
|
||||
impl_reflect_tuple! {0: A, 1: B, 2: C, 3: D}
|
||||
impl_reflect_tuple! {0: A, 1: B, 2: C, 3: D, 4: E}
|
||||
impl_reflect_tuple! {0: A, 1: B, 2: C, 3: D, 4: E, 5: F}
|
||||
impl_reflect_tuple! {0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G}
|
||||
impl_reflect_tuple! {0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H}
|
||||
impl_reflect_tuple! {0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I}
|
||||
impl_reflect_tuple! {0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J}
|
||||
impl_reflect_tuple! {0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K}
|
||||
impl_reflect_tuple! {0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L}
|
||||
@ -71,6 +71,10 @@ fn setup() {
|
||||
// `TupleStruct` is a trait automatically implemented for tuple structs that derive Reflect. This trait allows you
|
||||
// to interact with fields via their indices
|
||||
ReflectRef::TupleStruct(_) => {}
|
||||
// `Tuple` is a special trait that can be manually implemented (instead of deriving Reflect). This exposes "tuple"
|
||||
// operations on your type, allowing you to interact with fields via their indices. Tuple is automatically
|
||||
// implemented for tuples of arity 12 or less.
|
||||
ReflectRef::Tuple(_) => {}
|
||||
// `List` is a special trait that can be manually implemented (instead of deriving Reflect). This exposes "list"
|
||||
// operations on your type, such as indexing and insertion. List is automatically implemented for relevant core
|
||||
// types like Vec<T>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user