渡劫飞升 發表於 2024-4-22 09:32:23

Rust常用特型之ToOwned特型示例详解

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>ToOwned</li><li>Humble Cow</li></ul></div><blockquote><p>在Rust标准库中,存在很多常用的工具类特型,它们能帮助我们写出更具有Rust风格的代码。</p></blockquote>
<p class="maodian"></p><h2>ToOwned</h2>
<p>这次我们来学一个和<code>Borrow</code>特型相关的特型,叫<code>ToOwned</code>类型。看字面意思<code>Borrow</code>是代表借出,而<code>ToOwned</code>代表去拥有它。</p>
<p>在Rust中,假定某类型实现了<code>Clone</code>特型,如果给你一个对它引用,那我们得到它指向内容的备份的最常见方式是调用其<code>clone()</code>函数。但是如果你想克隆<code>&amp;str</code>或者<code>&amp;</code>时会发生什么呢?你的目的可能是想得到一个<code>String</code>或者<code>Vec&lt;i32&gt;</code>。但是根据<code>Clone</code>特型的定义,你无法得到它们。根据定义,对一个<code>&amp;T</code>调用<code>clone()</code> 会返回一个<code>T</code>的值,也就是说会返回<code>str</code>或者<code></code>。而我们前面学习<code>Sized</code>特型的时候提到过,<code>str</code>或者<code></code> 是切片类型,无固定大小,是不能保存在变量中或者作为函数结果返回的。</p>
<p><code>std::borrow::ToOwned</code> 特型提供了一个稍微宽松的方法将一引用转换为可拥有的值。</p>
<div class="jb51code"><pre class="brush:plain;">trait ToOwned {
type Owned: Borrow&lt;Self&gt;;
fn to_owned(&amp;self) -&gt; Self::Owned;
}</pre></div>
<p>上面的定义中,<code>to_owned</code>函数返回一个类型为<code>Self::Owned</code>的新鲜值,但是这个<code>Owned</code>可不是任意类型,它有个类型约束,也就是实现了<code>Borrow&lt;Self&gt;</code>. 也就是说A能借出<code>B/&amp;B</code>(实现了<code>Borrow&lt;B&gt;</code>),B才能拥有A.</p>
<p>例如,你可以从<code>Vec&lt;T&gt;</code>中借出一个<code>&amp;</code>(这里的泛型U 为 <code></code> ),因此<code></code>可以实现<code>ToOwned&lt;Owned=Vec&lt;T&gt;&gt;</code>,只要<code>T</code>实现了<code>Clone</code>特型。这里为什么要对<code>T</code>限制呢?毕竟你要得到一个备份,如果一个<code>T</code>不能克隆,那么这个备份是无法实现的,因为需要把切片的元素复制到新的向量中去。相似的,<code>str</code>实现了<code>ToOwned&lt;Owned=String&gt;</code>,因此我们可以调用<code>&amp;str</code>的<code>to_owned</code>函数得到一个全新的字符串。<code>Path</code>也实现了<code>ToOwned&lt;Owned=PathBuf&gt;</code>,我们也可以从<code>Path</code>引用中得到一个全新的<code>PathBuf</code>值。</p>
<p class="maodian"></p><h2>Humble Cow</h2>
<p><code>Borrow</code>和<code>ToOwned</code>联动可以实现一个很有意思的类型,<code>Cow</code>,注意它不是奶牛的意思,而是指 <code>clone on write</code> 我们趁热来学习它。</p>
<p>充分利用 Rust 需要深思熟虑所有权问题,例如某个函数是否应该通过引用或值接收参数。通常你能确定使用其中的一种或者另一种(使用引用还是值),函数的参数类型代表了你的决定。但是存在这样一些场景,你只有在运行时才知道到底是需要借用还是引用,这时,<code>std::borrow::Cow</code>类型就派上用场了,它的定义如下:</p>
<div class="jb51code"><pre class="brush:plain;">enum Cow&lt;'a, B: ?Sized&gt;
where B: ToOwned
{
Borrowed(&amp;'a B),
Owned(&lt;B as ToOwned&gt;::Owned),
}</pre></div>
<p>这里可以看到,Cow是一个枚举,有两个变量,分别代表借用和拥有。其<code>Borrow</code>变量绑定了一个<code>&amp;B</code>(这里先忽视生命周期标记),这个B是个泛型,它的约束为<code>B: ToOwned</code>。它的目标类型我们先假定为U,那么U 必定实现了<code>Borrow&lt;B&gt;</code>。</p>
<p>它的第二个枚举变量为<code>Owned</code>,绑定了一个<code>&lt;B as ToOwned&gt;::Owned</code>的值,也就是U的值,所以<code>Owned</code>变量可以写成<code>Owned&lt;U&gt;</code>。其中可以从U借出B,当然,也可以从B拥有U的新值。</p>
<p>第一个枚举变量,是绑定了&amp;B,因此我们可以很方便的得到<code>&amp;B</code>,第二个变量,是绑定了U,然而U又可以借出B,因此我们仍然可以很容易的得到<code>&amp;B</code>( 通过U的borrow() 函数)。两个变量都可以方便的得到<code>&amp;B</code>,因此它也实现了<code>Deref</code>特型,这样你可以直接在Cow上调用B的相关函数,而不管Cow是借用了B还是拥有了U。</p>
<p>你还可以在<code>Cow</code>类型的值上调用<code>to_mut</code>函数得到一个<code>&amp;mut B</code>。 如果<code>Cow</code>变量刚好好<code>Borrowed</code>,则<code>to_mut</code>函数会先调用<code>&amp;B</code>的<code>to_owned</code>方法得到它自己拥有的一个U的Copy,并对原来的变量进行重新赋值,这样就从<code>Borrowed</code>变量转换成了<code>Owned</code>变量,然后再从新拥有的U的值中借出一个mut 引用。这里正是<code>clone on write</code>的含义所在(写时clone).</p>
<p>我们来看一下这个<code>Deref</code>的实现代码:</p>
<div class="jb51code"><pre class="brush:plain;">#
impl&lt;B: ?Sized + ToOwned&gt; Deref for Cow&lt;'_, B&gt;
where
    B::Owned: Borrow&lt;B&gt;,
{
    type Target = B;
    fn deref(&amp;self) -&gt; &amp;B {
      match *self {
            Borrowed(borrowed) =&gt; borrowed,
            Owned(ref owned) =&gt; owned.borrow(),
      }
    }
}</pre></div>
<p>你代码中我们可以看到,如果<code>Cow</code>是引用 ,直接将这个引用返回,如果是拥有的U值,则从U值借出,这里有一个细节:</p>
<p><code>Owned(ref owned) =&gt; owned.borrow(),</code> 因为我们的deref函数接收参数为<code>&amp;self</code>,因此我们无法在函数内部消耗掉<code>Cow</code>本身,而<code>match</code>直接匹配时便会消耗这个值,因此为了阻止这种行为,添加了<code>ref owned</code>,代表这个<code>owned</code>只是获取一个引用 ,因此这里的owned的类型其实为<code>&amp;U</code>,所以直接调用其borrow()函数也就得到了一个&amp;B. 注意borrow函数也是接收一个引用而非值作为参数。</p>
<p><code>to_mut</code>函数的解释为:</p>
<blockquote><p>Acquires a mutable reference to the owned form of the data.</p>
<p>Clones the data if it is not already owned.</p></blockquote>
<p>使用示例为:</p>
<div class="jb51code"><pre class="brush:plain;">use std::borrow::Cow;
let mut cow = Cow::Borrowed("foo");
cow.to_mut().make_ascii_uppercase();
assert_eq!(
cow,
Cow::Owned(String::from("FOO")) as Cow&lt;'_, str&gt;
);</pre></div>
<p>通过上面的示例我们可以看到,就算我们的cow是<code>Borrowed</code>变量,拥有一个共享的引用,到最后也变成了一个<code>Owned</code>变量。</p>
<p>我们来看一下实现过程:</p>
<div class="jb51code"><pre class="brush:plain;">#
pub fn to_mut(&amp;mut self) -&gt; &amp;mut &lt;B as ToOwned&gt;::Owned {
    match *self {
      Borrowed(borrowed) =&gt; {
            *self = Owned(borrowed.to_owned());
            match *self {
                Borrowed(..) =&gt; unreachable!(),
                Owned(ref mut owned) =&gt; owned,
            }
      }
      Owned(ref mut owned) =&gt; owned,
    }
}</pre></div>
<p>这里 如果是Borrowed,则首先会to_owned得到U的一个新值,然后再将self重新赋值为Owned,然后再重新对self进行match操作,</p>
<p>此时已经是一个Owned,所以直接借出了ref mut,注意因为match操作会消耗值,所以这里的<code>Owned(ref mut owned) =&gt; owned,</code> 中加了ref 代表是一个引用,结合上例,我们就得到了一个&amp;mut String。</p>
<p>相似的,<code>Cow</code>也实现了<code>into_owned</code>方法将引用转换为一个拥有的值。如果必须,则可以将值的所有权转移给调用者,在这个过程中<code>Cow</code>本身的值会被消耗掉。</p>
<p>Cow一个常见的用法是返回一个静态的字符串文字值常量或者一个动态的字符串。例如,假定你需要将一个枚举类型转换成一个消息,枚举的大多数变量都可用于固定的字符串,但是有一些变量或者一些额外的信息,因此你可以返回一个<code>Cow&lt;&#39;static str&gt;</code>。</p>
<div class="jb51code"><pre class="brush:plain;">use std::path::PathBuf;
use std::borrow::Cow;
fn describe(error: &amp;Error) -&gt; Cow&lt;'static, str&gt; {
match *error {
      Error::OutOfMemory =&gt; "out of memory".into(),
      Error::StackOverflow =&gt; "stack overflow".into(),
      Error::MachineOnFire =&gt; "machine on fire".into(),
      Error::Unfathomable =&gt; "machine bewildered".into(),
      Error::FileNotFound(ref path) =&gt; {
      format!("file not found: {}", path.display()).into()
    }
}
}</pre></div>
<p>上面的代码使用了<code>Cow</code>的<code>Into</code>特型实现来构造值。这里其实是<code>Cow</code>的<code>From</code>实现,然后相对应的<code>&amp;str</code>就有了<code>Into</code>实现。这个Match的绝大多数分支都返回一个静态分配的文字串文本用于<code>Cow::Borrowed</code>绑定,只有最后一个分支返回一个<code>String</code>用于<code>Owned</code>变量绑定。</p>
<p><code>describe</code>函数的调用者不用管返回的到底Cow的哪个变量,它只用简单的将返回值看成是<code>&amp;str</code>就行了,例如:</p>
<p><code>println!(&quot;Disaster has struck: {}&quot;, describe(&amp;error));</code></p>
<p>如果你需要一个拥有的值,调用<code>into_owned</code>函数就可,例如 <code>(describe(&amp;error).into_owned()</code> 就返回一个<code>String</code>。</p>
<p>使用Cow可以让<code>describle</code>函数和它的调用者直到在需要时才会分配内存来保存新生成的字符串。</p>
<p>到此这篇关于Rust常用特型之ToOwned特型的文章就介绍到这了,更多相关Rust ToOwned特型内容请搜索琼殿技术社区以前的文章或继续浏览下面的相关文章希望大家以后多多支持琼殿技术社区!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>Rust常用特型之Drop特型</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: Rust常用特型之ToOwned特型示例详解