2017-11-25 158 views
16

我想測試Rc<Trait>類型的兩個對象是否包含同一個具體類型的實例,所以我比較指向Rc中的對象的指針是否相等。它似乎工作正常,如果所有代碼駐留在相同的箱子,但失敗時涉及多個板條箱。爲什麼可以用==比較兩個看似相同的指針返回false?

這是板條箱mcve的(src/lib.rs)實現:

use std::rc::Rc; 

pub trait ObjectInterface {} 

pub type Object = Rc<ObjectInterface>; 

pub type IntObject = Rc<i32>; 

impl ObjectInterface for i32 {} 

/// Test if two Objects refer to the same instance 
pub fn is_same(left: &Object, right: &Object) -> bool { 
    let a = left.as_ref() as *const _; 
    let b = right.as_ref() as *const _; 
    let r = a == b; 
    println!("comparing: {:p} == {:p} -> {}", a, b, r); 
    r 
} 

pub struct Engine { 
    pub intval: IntObject, 
} 

impl Engine { 
    pub fn new() -> Engine { 
     Engine { 
      intval: Rc::new(42), 
     } 
    } 

    pub fn run(&mut self) -> Object { 
     return self.intval.clone(); 
    } 
} 

我測試用下面的代碼的執行(tests/testcases.rs):

extern crate mcve; 

use mcve::{is_same, Engine, Object}; 

#[test] 
fn compare() { 
    let mut engine = Engine::new(); 

    let a: Object = engine.intval.clone(); 
    let b = a.clone(); 
    assert!(is_same(&a, &b)); 

    let r = engine.run(); 
    assert!(is_same(&r, &a)); 
} 

運行在下面的輸出測試結果:

comparing: 0x7fcc5720d070 == 0x7fcc5720d070 -> true 
comparing: 0x7fcc5720d070 == 0x7fcc5720d070 -> false 
thread 'compare' panicked at 'assertion failed: is_same(&r, &a)' 

比較運算符==返回false怎麼可能,儘管指針似乎是相同的?

幾個意見:

  • 比較返回true當兩個物體(ab)住在同一個箱子。但是,在另一個包中定義的功能Engine::run返回其中一個對象(r)時,比較返回false
  • 當我將測試功能放入lib.rs時,測試正確通過。
  • 該問題可以通過定義struct Engine { intval: Object }來解決,但我仍然對爲什麼感興趣。

回答

18

何時是「指針」而不是「指針」?當它是一個胖指針Object是一個特徵,這意味着&Object特徵對象。特徵對象由兩個機器指針組成:一個用於具體數據,另一個用於vtable,這是具體值的一組特定實現。這個雙指針被稱爲胖指針。

使用夜間的編譯器和std::raw::TraitObject,可以看到的差異:

#![feature(raw)] 

use std::{mem, raw}; 

pub fn is_same(left: &Object, right: &Object) -> bool { 
    let a = left.as_ref() as *const _; 
    let b = right.as_ref() as *const _; 
    let r = a == b; 
    println!("comparing: {:p} == {:p} -> {}", a, b, r); 

    let raw_object_a: raw::TraitObject = unsafe { mem::transmute(left.as_ref()) }; 
    let raw_object_b: raw::TraitObject = unsafe { mem::transmute(right.as_ref()) }; 
    println!(
     "really comparing: ({:p}, {:p}) == ({:p}, {:p})", 
     raw_object_a.data, raw_object_a.vtable, 
     raw_object_b.data, raw_object_b.vtable, 
    ); 

    r 
} 
comparing: 0x101c0e010 == 0x101c0e010 -> true 
really comparing: (0x101c0e010, 0x1016753e8) == (0x101c0e010, 0x1016753e8) 
comparing: 0x101c0e010 == 0x101c0e010 -> false 
really comparing: (0x101c0e010, 0x101676758) == (0x101c0e010, 0x1016753e8) 

事實證明,(至少在鏽病1.22.1)每個碼生成單元創建一個單獨的虛函數表!這解釋了爲什麼它在全部在同一個模塊中時工作。如果這是一個錯誤,則有active discussion

當您使用#[inline]註釋newrun功能時,消費者將使用該vtable。


由於Francis Gagné said

您可以更改as *const _as *const _ as *const()打開脂肪指針變成一個普通的指針,如果你只關心價值的地址。

+0

呃。這是有見地的,謝謝!雖然知道脂肪指針我並不認爲他們是問題的根源。我已經和他們玩過了,看起來'IntObject'有不同的vtable,具體取決於創建它們的創建。這是......意想不到的......看起來對象身份並不像希望的那樣直截了當。 – kazemakase

+5

@kazemakase如果你只關心值的地址,你可以把'as * const _'改成'as * const _ as * const()'把fat指針變成常規指針。 –

+0

@FrancisGagné非常好!這應該適用於我的用例。 – kazemakase

相關問題