2016-11-30 25 views
1

例如,我正在創建一個計算圖,這是一個將方程建模爲圖的程序,因此可以區分它們。我已經重載了運算符,因此添加圖形節點會創建新的圖形節點。但是,我發現自己正在與借閱檢查員作鬥爭。下面的代碼:如何超載爲Rust中的計算圖節點添加

use std::ops::Add; 

#[derive(Debug)] 
pub enum Expr<'a> { 
    Constant(f32), 
    //  ^^^--this is a simplified version. In the real version, 
    //   Constant holds large matrices and I would like to 
    //   avoid copying it. 
    Add(&'a Node<'a>, &'a Node<'a>), 
} 

#[derive(Debug)] 
pub struct Node<'a> { 
    pub body: Expr<'a> 
} 

impl<'a> Add for &'a Node<'a> { 
    type Output = Node<'a>; 
    fn add(self, other: &'a Node<'a>) -> Node<'a> { 
     Node { body: Expr::Add(self, other) } 
    } 
} 

fn main() { 
    let a = Node { body: Expr::Constant(1.) }; 
    let f: Node = &(&a + &a) + &a; 
    println!("f: {:#?}", f); 
} 

正如你所看到的,+需要採取兩種graph::Node結構引用,並返回一個graph::Node結構。

error: borrowed value does not live long enough 
    --> src/main.rs:25:20 
    | 
25 |  let f: Node = &(&a + &a) + &a; 
    |     ^^^^^^^^^  - temporary value only lives until here 
    |     | 
    |     temporary value created here 
26 | } 
    | - temporary value needs to live until here 
    | 
    = note: consider using a `let` binding to increase its lifetime 

我使用的引用,因爲我想避免複製Node結構:由於&(&a + &a)被創建,並在同一行死了,我得到這個錯誤。這裏的代碼略有簡化;在我正在使用的版本中,這些可以保存大量數據。

我試過靜態引用和Rc指針,並沒有成功。當我嘗試使用Rc指針,編譯器抱怨超載Add

error[E0117]: only traits defined in the current crate can be implemented for arbitrary types 
    --> function.rs:86:1 
    | 
86 | impl Add for Rc<Function> { 
    |^impl doesn't use types inside crate 
    | 
    = note: the impl does not reference any types defined in this crate 

目前我只是定義每個操作這實在是凌亂的新變量:

let f1 = &a + &a; 
... 
let f = &a + f1 

什麼是理想的是如果我可以完全避免使用引用(這會使代碼更清晰)。至少,我需要通過添加現有節點來生成新節點的方法。

+0

爲什麼不只是做了錯誤消息告訴您,並使用'let'結合? '讓f =&a +&a;讓f2 =&f +&a;'。爲什麼你首先存儲對'Node's的引用,而不是直接存儲它們?爲什麼不爲不引用實現'Add'? – Shepmaster

+0

感謝@Shepmaster的建議。所以我正在使用'f1 = ...; f2 = ...'。當你處理一個複雜的大方程時,它會使代碼難於閱讀。存儲引用的原因是使用add-for非引用需要一個副本,並且對性能有問題(我簡化了代碼,但在實際版本中,圖節點可以包含大型矩陣)。 – ethanabrooks

+0

使用Rc時出了什麼問題? –

回答

1

首先讓我們來認識到NodeExpr是多餘的:

use std::ops::Add; 

#[derive(Debug)] 
pub enum Expr<'a> { 
    Constant(f32), 
    Add(&'a Expr<'a>, &'a Expr<'a>), 
} 

impl<'a> Add for &'a Expr<'a> { 
    type Output = Expr<'a>; 
    fn add(self, other: &'a Expr<'a>) -> Expr<'a> { 
     Expr::Add(self, other) 
    } 
} 

fn main() { 
    let a = Expr::Constant(1.); 
    let f = &(&a + &a) + &a; 
    println!("f: {:#?}", f); 
} 

I am using references because I want to avoid copying Node structs. The code here is slightly simplified; in the version I'm working with, these can hold a lot of data.

這就是爲什麼你的代碼不能正常工作。考慮Add以下完全有效的實現:

fn add(self, other: &'a Expr<'a>) -> Expr<'a> { 
    Expr::Constant(42.0) 
} 

然後我們調用和返回它,因爲這表達上半年:

let f = &(&a + &a) + &a; 
//  ^-------- here 

哪裏存儲的是,Constant?只有沒有所有者的臨時價值。當你做第二個另外,你試圖引用那個必須在f有效的整個時間生存的引用。除非該表達式結束後該值立即被刪除,因爲沒有任何東西可以擁有它。因此你被阻止做這種事。

此外,&'a Expr<'a>意味着生命期'a必須是參考生命週期和包含數據的生命週期的統一。這意味着'a將縮短到傳入的Expr的壽命,在這種情況下,在本聲明的結尾處再次結束。

通常情況下,每當我看到&'a Foo<'a>,我想兩件事情之一:

  1. 這應該是Foo<'a> - Foo只是周圍的其他類型的引用的包裝,不需要有其他的參考。
  2. 這應該是&'a Foo<'b> - 我們擁有的東西具有參考參考,他們有不同的壽命。

後者已經不在這裏工作 - 這是複雜的解釋,但最終你最終像

#[derive(Debug)] 
pub enum Expr<'e, 'd> { 
    Constant(f32), 
    Add(&'e Expr<'e, 'd>, &'e Expr<'e, 'd>), 
} 

其中從未實際使用'd參數

做第一個選項是好的,如果你能擁有自己的表達層次外側的大數據:

use std::ops::Add; 

type LargeMatrix = Vec<u8>; 

#[derive(Clone, Debug)] 
pub enum Expr<'a> { 
    Constant(&'a LargeMatrix), 
    Add(Box<Expr<'a>>, Box<Expr<'a>>), 
} 

impl<'a> Add for Expr<'a> { 
    type Output = Expr<'a>; 
    fn add(self, other: Expr<'a>) -> Expr<'a> { 
     Expr::Add(Box::new(self), Box::new(other)) 
    } 
} 

fn main() { 
    let m = LargeMatrix::new(); 

    let a = Expr::Constant(&m); 
    let f = (a.clone() + a.clone()) + a; 
    println!("f: {:#?}", f); 
} 

如果你想在表達式樹擁有大數據以及向子樹的引用,那麼你可以使用Rc,如評論中所建議的那樣。每當數據被克隆,只有參考計數器遞增,所以沒有大的副本將:

use std::ops::Add; 
use std::rc::Rc; 

type LargeMatrix = Vec<u8>; 

#[derive(Clone, Debug)] 
pub enum Expr { 
    Constant(Rc<LargeMatrix>), 
    Add(Rc<Expr>, Rc<Expr>), 
} 

impl Add for Expr { 
    type Output = Expr; 
    fn add(self, other: Expr) -> Expr { 
     Expr::Add(Rc::new(self), Rc::new(other)) 
    } 
} 

fn main() { 
    let a = Expr::Constant(Rc::new(LargeMatrix::new())); 
    let f = (a.clone() + a.clone()) + a; 
    println!("f: {:#?}", f); 
} 

然後,您可以選擇執行另外的參考:

impl<'a> Add for &'a Expr { 
    type Output = Expr; 
    fn add(self, other: &'a Expr) -> Expr { 
     Expr::Add(Rc::new(self.clone()), Rc::new(other.clone())) 
    } 
} 

,其他兩個變種(&Expr + Expr,Expr + &Expr),如果你需要。

0

感謝大家的幫助和建議。我不知道克隆Rc沒有克隆內部值。所以,我能夠重新實現我的代碼如下:

use std::ops::Add; 
use std::rc::Rc; 

#[derive(Debug)] 
pub enum Expr { 
    Constant(f32), 
    //  ^^^--this is a simplified version. In the real version, 
    //   Constant holds large matrices and I would like to 
    //   avoid copying it. 
    Add(Node, Node), // USED TO BE ADD(&NODE, &NODE) 
} 

#[derive(Debug, Clone)] // CLONE IS NEW 
pub struct Node {  
    pub body: Rc<Expr> // CHANGED TO RC 
} 

// THIS IS NEW 
impl Add for Node { 
    type Output = Node; 
    fn add(self, other: Node) -> Node { &self + &other } 
} 

impl<'a> Add for &'a Node { 
    type Output = Node; 
    fn add(self, other: &Node) -> Node { 
     Node { 
      body: Rc::new(Expr::Add(self.clone(), other.clone())) 
     } 
    } 
} 

fn main() { 
    let a = Node { body: Rc::new(Expr::Constant(1.)) }; 
    let f: Node = &a + &a + a; // <-- basically, the desired outcome 
    println!("f: {:#?}", f); 
} 

這將是很好,如果我能以某種方式得到的f定義擺脫& S的,但唯一的辦法似乎是實施Copy。不幸的是,這是不可能在現實的版本,因爲Node有一個額外的領域,output,與Rc<RefCell<Option<Constant>類型,其中Constant是一個枚舉。我知道,不是最好的,但不幸的是我需要別名可變性,這似乎是唯一的方法。無論如何,這種類型的怪物不能實現Copy。任何想法在這方面是值得歡迎的,但除此之外,感謝您的幫助!