49
loading...
This website collects cookies to deliver better user experience
Deref
is categorized as an ops
module. If you look at the documentation, you will see that this module defines the trait fr all the overloadable operators. For example, Add trait
corresponds to +
, while Deref trait
corresponds to a shared(immutable) borrowing dereference operation, such as *v
. Correspondingly, there is also the DerefMut trait
, which corresponds to the dereferencing operation of exclusive(mutable) borrowing. Since the Rust ownership semantics is a language feature throughout , the semantics of Owner
/ immutable borrowing(&T)
/ mutable borrowing(&mut T)
all appear together.AsRef
is grouped under the convert module. if you look at the documentation, you will see that traits related to type conversions are defined in this module. For example, the familiar "From/To", "TryFrom/TryTo" and "AsRef/AsMut" also appear in pairs here, indicating that the feature is releated to type conversions. Based on the naming rules in the Rust API Guidelines , wen can infer that methods starting with as_
represent conversions from borrow -> borrow
, i.e, reference -> reference
, and are overhead-free. And such conversions do not fail.Borrow
is categorized in the borrow module. The documentation for this module is very minimal, with a single sentence saying that this is for using borrowed data. So the trait is more or less related to expressing borrwo semantics. Three traits are provided: Borrow / BorrowMut/ ToOwned , which corresponds exactly to the ownership semantics.Cow
is also classified as a borrow module. According to the description, Cow
is a clone-on-write smart pointer. The main reason for putting it in the borrow module is to use borrowing as much as possible and avoid copying, as an optimization.pub trait Deref {
type Target: ?Sized;
#[must_use]
pub fn deref(&self) -> &Self::Target;
}
Deref
contains only a deref
method signature. The beauty of this trait is that it is called "implicitly" by the compiler, officially called "deref coercion ". use std::ops::Deref;
struct DerefExample<T> {
value: T
}
impl<T> Deref for DerefExample<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.value
}
}
let x = DerefExample { value: 'a' };
assert_eq!('a', *x);
DerefExample
structure implements the Deref
trait, so it can be executed using the dereference operator *
. In the example, the value of the field value is returned directly.DerefExample
has a pointer-like behavior , because it implements Deref
, because it can be dereferenced. DerefExample
also becomes a kind of smart pointer. This is one way to identify if a type is a smart pointer, by seeing if it implements Deref
. But not all smart pointers implement Deref
, some implent Drop
, or both.Deref
.T
implements Deref<Target=U>
, and x
is an instance of type T
, then.*x
(when T
is neither a reference nor a primitive pointer) is equivalent to *Deref::deref(&x)
. &T
is forced to be converted to the value of &U
. (deref coercion).T
implements all the (immutable) methods of U
. Deref
is that it enhances the Rust development experience. A typical example from the standard library is that Vec<T>
shares all the methods of slice
by implemented Deref
.impl<T, A: Allocator> ops::Deref for Vec<T, A> {
type Target = [T];
fn deref(&self) -> &[T] {
unsafe { slice::from_raw_parts(self.as_ptr(), self.len) }
}
}
len()
, is actually defined in the slice
module. In Rust, when executing .
call, or at the function argument position, the compiler automatically performs the implicit act of deref coercion. so it is equivalent to Vec<T>
having the slice
method as well.fn main() {
let a = vec![1, 2, 3];
assert_eq!(a.len(), 3); // 当 a 调用 len() 的时候,发生 deref 强转
}
Deref
is one of them, and its implicit coercion make smart pointers easy to use.fn main() {
let h = Box::new("hello");
assert_eq!(h.to_uppercase(), "HELLO");
}
Box<T>
, instead of manually dereferencing T
inside to manipulate it, as if the outer layer of Box<T>
is transparent, we can manipulate T directly.fn uppercase(s: &str) -> String {
s.to_uppercase()
}
fn main() {
let s = String::from("hello");
assert_eq!(uppercase(&s), "HELLO");
}
uppercase
method above is obviously &str
, but the actual type passed in the main function is &String
, so why does it compile successfully? It is because String
implementsDeref
.impl ops::Deref for String {
type Target = str;
#[inline]
fn deref(&self) -> &str {
unsafe { str::from_utf8_unchecked(&self.vec) }
}
}
Deref
. But some people may mistake it for inheritance. Big mistake.Deref
to simulate inheritance.AsRef
.pub trait AsRef<T: ?Sized> {
fn as_ref(&self) -> &T;
}
AsRef
can be used for conversions. Compared to Deref
, which has an implicit behavior, AsRef
is an explicit conversion.fn is_hello<T: AsRef<str>>(s: T) {
assert_eq!("hello", s.as_ref());
}
fn main() {
let s = "hello";
is_hello(s);
let s = "hello".to_string();
is_hello(s);
}
is_hello
is a generic function. The conversion is achieved by qualifying T: AsRef<str>
and using an explicit call like s.as_ref()
inside the function. Either String
or str
actually implements the AsRef
trait.AsRef
? Why not just use &T
?pub struct Thing {
name: String,
}
impl Thing {
pub fn new(name: WhatTypeHere) -> Self {
Thing { name: name.some_conversion() }
}
new
function name has the following options for the type parameter.&str
. In this case, the caller needs to pass in a reference. But in order to convert to String, the called party (callee) needs to control its own memory allocation, and will have a copy.String
. In this case, the caller is fine passing String, but if it is passing a reference, it is similar to case 1. T: Into<String>
. In this case, the caller can pass &str
and String
, but there will be memory allocation and copying during the type conversion as well.T: AsRef<str>
. Same as case 3. T: Into<Cow<'a, str>>
, where some allocations can be avoided. Cow
will be described later.&str
and will use it no matter what. There are trade-offs here.&str
.Deref
and AsRef
.let element: &Element = ...;
element.append_child(..); // call a method on `Node`
method_expecting_a_node(&element); // coerce to `&Node` implicitly
let node: &Node = &element; // explicitly coerce to `&Node`
web_sys::Element
, then you can get web_sys::Node
implicitly by using deref..
operation to transparently use the parent class.AsRef
conversions are also implemented in web-sys for various types.impl AsRef<HtmlElement> for HtmlAnchorElement
impl AsRef<Element> for HtmlAnchorElement
impl AsRef<Node> for HtmlAnchorElement
impl AsRef<EventTarget> for HtmlAnchorElement
impl AsRef<Object> for HtmlAnchorElement
impl AsRef<JsValue> for HtmlAnchorElement
.as_ref()
.Stream / headers/ URL
, so it implements AsRef<Url>
, AsRef<Headers>
, and AsyncRead
. Similarly, Response is a combination of Stream / headers/ Status Code
. So it implements AsRef<StatusCode>
, AsRef<Headers>
, and AsyncRead
.fn forwarded_for(headers: impl AsRef<http_types::Headers>) {
// get the X-forwarded-for header
}
// 所以,forwarded_for 可以方便处理 Request/ Response / Trailers
let fwd1 = forwarded_for(&req);
let fwd2 = forwarded_for(&res);
let fwd3 = forwarded_for(&trailers);
Borrow
.pub trait Borrow<Borrowed: ?Sized> {
fn borrow(&self) -> &Borrowed;
}
AsRef
:pub trait AsRef<T: ?Sized> {
fn as_ref(&self) -> &T;
}
T
in the borrow()
method by implementing Borrow<T>
, expressing the semantics that it can be borrowed, rather than converted to some type T
. A type can be freely borrowed as several different types, or it can be borrowed in a mutable way.HashMap<K, V>
stores key-value pairs, and its API should be able to retrieve the corresponding value in the HashMap properly using either the key's own value or its reference. Since the HashMap has to hash and compare keys, it must require that both the key's own value and the reference behave the same when hashed and compared.use std::borrow::Borrow;
use std::hash::Hash;
pub struct HashMap<K, V> {
// fields omitted
}
impl<K, V> HashMap<K, V> {
// The insert method uses the key's own value and takes ownership of it.
pub fn insert(&self, key: K, value: V) -> Option<V>
where K: Hash + Eq
{
// ...
}
// If you use the get method to get the corresponding value by key, you can use the reference of key, which is denoted by &Q here
// and requires Q to satisfy `Q: Hash + Eq + ?Sized`
// As for K, it is expressed as a borrowed data of Q by `K: Borrow<Q>`.
// So, the hash implementation of Q is required to be the same as K
pub fn get<Q>(&self, k: &Q) -> Option<&V>
where
K: Borrow<Q>,
Q: Hash + Eq + ?Sized
{
// ...
}
}
Hash
and Eq
in the example.// Can this structure be used as the key of a HashMap?
pub struct CaseInsensitiveString(String);
// It implements PartialEq without problems
impl PartialEq for CaseInsensitiveString {
fn eq(&self, other: &Self) -> bool {
// Note that the comparison here is required to ignore ascii case
self.0.eq_ignore_ascii_case(&other.0)
}
}
impl Eq for CaseInsensitiveString { }
// Implementing Hash is no problem
// But since PartialEq ignores case, the hash calculation must also ignore case
impl Hash for CaseInsensitiveString {
fn hash<H: Hasher>(&self, state: &mut H) {
for c in self.0.as_bytes() {
c.to_ascii_lowercase().hash(state)
}
}
}
Borrow<str>
?Borrow<str>
must not be implemented for CaseInsensitiveString, so CaseInsensitiveString cannot be used as a key for a HashMap. What happens if we force Borrow<str>
to be used? It will fail due to case difference when determining the key.Borrow
is a bit stricter and represents a completely different semantics than AsRef
.Cow
.pub enum Cow<'a, B>
where
B: 'a + ToOwned + ?Sized,
{
Borrowed(&'a B),
Owned(<B as ToOwned>::Owned),
}
Cow
provides methods to do cloning and avoid repeated cloning.Cow
is designed to improve performance (reduce replication) while increasing flexibility, because most of the time, business scenarios are read more and write less. With Cow
, this can be achieved in a uniform, canonical form, where object replication is done only once when a write is needed. This may reduce the number of replications significantly.Cow<T>
can directly call the immutable methods of T
, since Cow
, an enumeration, implements Deref
..to_mut()
method can be used to obtain a mutable borrow with an ownership value when T
needs to be modified.
.to_mut()
does not necessarily result in a Clone..to_mut()
when ownership is already present is valid, but does not produce a new Clone..to_mut()
will produce only one Clone..into_owned()
can be used to create a new owned object when T
needs to be modified, a process that often implies a memory copy and the creation of a new object.
Cow
was in borrowed state.self
, will "consume" the original instance of that type, after which the life cycle of the original instance of that type will end, and cannot be called more than once on Cow
.use std::borrow::Cow;
// Use Cow for the return value to avoid multiple copies
fn remove_spaces<'a>(input: &'a str) -> Cow<'a, str> {
if input.contains(' ') {
let mut buf = String::with_capacity(input.len());
for c in input.chars() {
if c != ' ' {
buf.push(c);
}
}
return Cow::Owned(buf);
}
return Cow::Borrowed(input);
}
AsRef
" discussion in our previous article, there are trade-offs and no one-size-fits-all standard answer.