ฉันจะพิมพ์ประเภทของตัวแปรใน Rust ได้อย่างไร


238

ฉันมีดังต่อไปนี้:

let mut my_number = 32.90;

ฉันจะพิมพ์ประเภทของได้my_numberอย่างไร

การใช้typeและtype_ofไม่ทำงาน มีวิธีอื่นที่ฉันสามารถพิมพ์ชนิดของตัวเลขได้หรือไม่

คำตอบ:


177

หากคุณเพียงต้องการหาประเภทของตัวแปรและเต็มใจที่จะทำมันในเวลารวบรวมคุณสามารถทำให้เกิดข้อผิดพลาดและทำให้คอมไพเลอร์รับมัน

ตัวอย่างเช่นตั้งค่าตัวแปรเป็นประเภทที่ไม่ทำงาน :

let mut my_number: () = 32.90;
// let () = x; would work too
error[E0308]: mismatched types
 --> src/main.rs:2:29
  |
2 |     let mut my_number: () = 32.90;
  |                             ^^^^^ expected (), found floating-point number
  |
  = note: expected type `()`
             found type `{float}`

หรือเรียกวิธีการที่ไม่ถูกต้อง :

let mut my_number = 32.90;
my_number.what_is_this();
error[E0599]: no method named `what_is_this` found for type `{float}` in the current scope
 --> src/main.rs:3:15
  |
3 |     my_number.what_is_this();
  |               ^^^^^^^^^^^^

หรือเข้าถึงฟิลด์ที่ไม่ถูกต้อง :

let mut my_number = 32.90;
my_number.what_is_this
error[E0610]: `{float}` is a primitive type and therefore doesn't have fields
 --> src/main.rs:3:15
  |
3 |     my_number.what_is_this
  |               ^^^^^^^^^^^^

สิ่งเหล่านี้เปิดเผยประเภทซึ่งในกรณีนี้จริง ๆ แล้วยังไม่ได้รับการแก้ไขอย่างเต็มที่ มันเรียกว่า "ตัวแปรจุดลอยตัว" ในตัวอย่างแรกและ " {float}" ในทั้งสามตัวอย่าง นี่เป็นประเภทที่ได้รับการแก้ไขบางส่วนซึ่งอาจสิ้นสุดf32หรือf64ขึ้นอยู่กับว่าคุณใช้งานอย่างไร “ {float}” ไม่ใช่ชื่อประเภทตามกฎหมายมันเป็นตัวยึดความหมาย“ ฉันไม่แน่ใจว่าสิ่งนี้คืออะไร” แต่เป็นเลขทศนิยม ในกรณีที่เป็นตัวแปรทศนิยมถ้าคุณไม่ได้ จำกัด มันจะเริ่มต้นที่f64¹ (ตัวอักษรจำนวนเต็มที่ไม่มีเงื่อนไขจะเป็นค่าเริ่มต้นi32)

ดูสิ่งนี้ด้วย:


¹อาจยังมีวิธีการที่ทำให้งงงันคอมไพเลอร์เพื่อที่จะไม่สามารถตัดสินใจระหว่างf32และf64; ฉันไม่แน่ใจ. มันเคยเป็นเรื่องง่ายเหมือน32.90.eq(&32.90)กัน แต่มันก็ปฏิบัติต่อทั้งสองอย่างในf64ขณะนี้และเป็นไปอย่างมีความสุขดังนั้นฉันจึงไม่รู้


4
:?มีมานานแล้วตอนนี้มีการใช้งานด้วยตนเอง แต่ที่สำคัญกว่านั้นstd::fmt::Debugการนำไปใช้ (สำหรับการ:?ใช้งานนั้น) สำหรับประเภทหมายเลขจะไม่มีคำต่อท้ายเพื่อระบุประเภทที่เป็นของมันอีกต่อไป
Chris Morgan

2
ฉันใช้เทคนิคเหล่านี้มากมายในการพยายามค้นหาประเภทของนิพจน์ แต่มันก็ไม่ได้ผลเสมอไปโดยเฉพาะเมื่อมีพารามิเตอร์ประเภทที่เกี่ยวข้อง ยกตัวอย่างเช่นผู้เรียบเรียงจะบอกฉันว่ามันคาดหวังสิ่งImageBuffer<_, Vec<_>>ที่ไม่ได้ช่วยฉันมากนักเมื่อฉันพยายามเขียนฟังก์ชั่นที่ใช้สิ่งเหล่านี้เป็นพารามิเตอร์ :()และนี้เกิดขึ้นในรหัสที่มิฉะนั้นรวบรวมจนกว่าฉันจะเพิ่ม ไม่มีทางไหนที่ดีกว่านี้เหรอ?
Christopher Armstrong

2
สิ่งนี้ดูเหมือนจะซับซ้อนและใช้งานง่าย มันจะเป็นเรื่องยากมากสำหรับตัวแก้ไขรหัสเช่น Emacs ให้ประเภทเมื่อเคอร์เซอร์วางอยู่บนตัวแปรเช่นในภาษาอื่น ๆ อีกมากมาย? หากคอมไพเลอร์สามารถบอกประเภทเมื่อเกิดข้อผิดพลาดแน่นอนมันควรจะรู้แล้วประเภทเมื่อไม่มีข้อผิดพลาดใด ๆ ?
xji

1
@JIXiang: เซิร์ฟเวอร์ภาษา Rust นั้นเกี่ยวกับการให้ข้อมูลนี้แก่ IDE แต่ยังไม่เป็นผู้ใหญ่ - การเปิดตัวครั้งแรกของอัลฟ่านั้นเป็นเพียงไม่กี่วันที่ผ่านมา ใช่นี่เป็นวิธี eldritch ใช่วิธีที่ลึกลับน้อยกว่าในการบรรลุเป้าหมายกำลังมาอย่างต่อเนื่อง
Chris Morgan

1
ฟังดูเหมือนแฮ็ค นี่เป็นวิธีที่ใช้สำนวนเพื่อตรวจสอบชนิดของตัวแปรหรือไม่?
สับสน

109

มีฟังก์ชั่นที่ไม่เสถียรstd::intrinsics::type_nameที่ทำให้คุณได้รับชื่อของประเภทถึงแม้ว่าคุณจะต้องใช้งานสร้างสนิมในยามค่ำคืน นี่คือตัวอย่าง:

#![feature(core_intrinsics)]

fn print_type_of<T>(_: &T) {
    println!("{}", unsafe { std::intrinsics::type_name::<T>() });
}

fn main() {
    print_type_of(&32.90);          // prints "f64"
    print_type_of(&vec![1, 2, 4]);  // prints "std::vec::Vec<i32>"
    print_type_of(&"foo");          // prints "&str"
}

@ vbo: ไม่จนกว่าจะมีความเสถียร บางสิ่งบางอย่างเช่นนี้ไม่น่าจะเสถียรได้สักพักถ้าเคย - และมันจะไม่ทำให้ฉันประหลาดใจถ้ามันไม่เสถียร ไม่ใช่สิ่งที่คุณควรทำจริงๆ
Chris Morgan

2
สำหรับสนิมคืน (1.3) มันใช้ได้เฉพาะเมื่อเปลี่ยนบรรทัดแรกเป็น#![feature(core_intrinsics)]
AT

1
@DmitriNesteruk: print_type_ofคือการอ้างอิง ( &T) ไม่ใช่ค่า ( T) ดังนั้นคุณจะต้องผ่าน&&strมากกว่า&str; ที่เป็นมากกว่าprint_type_of(&"foo") print_type_of("foo")
Chris Morgan

คุณพูดถูก 3 ปีผ่านไปและมันก็ยังไม่เสถียร
Anton Kochkov

5
std::any::type_nameเสถียรตั้งแต่เกิดสนิม 1.38: stackoverflow.com/a/58119924
ทิมโรบินสัน

66

คุณสามารถใช้std::any::type_nameฟังก์ชั่น สิ่งนี้ไม่ต้องการคอมไพเลอร์ยามค่ำคืนหรือลังภายนอกและผลลัพธ์ค่อนข้างถูกต้อง:

fn print_type_of<T>(_: &T) {
    println!("{}", std::any::type_name::<T>())
}

fn main() {
    let s = "Hello";
    let i = 42;

    print_type_of(&s); // &str
    print_type_of(&i); // i32
    print_type_of(&main); // playground::main
    print_type_of(&print_type_of::<i32>); // playground::print_type_of<i32>
    print_type_of(&{ || "Hi!" }); // playground::main::{{closure}}
}

ได้รับการเตือน: ตามที่กล่าวไว้ในเอกสารข้อมูลนี้จะต้องใช้เพื่อจุดประสงค์ในการแก้ไขปัญหาเท่านั้น:

สิ่งนี้มีไว้สำหรับใช้ในการวินิจฉัย ไม่ได้ระบุเนื้อหาและรูปแบบที่แน่นอนของสตริงนอกจากเป็นคำอธิบายที่ดีที่สุดของประเภท

หากคุณต้องการเป็นตัวแทนประเภทของคุณไปอยู่เหมือนกันระหว่างรุ่นคอมไพเลอร์ที่คุณควรใช้ลักษณะเช่นในคำตอบของ phicr


1
คำตอบที่ดีที่สุดสำหรับฉันเป็น devs ส่วนใหญ่ต้องการที่จะใช้สำหรับการดีบักวัตถุประสงค์เช่นการพิมพ์ความล้มเหลวของการแยก
Kaiser

สิ่งที่ฉันต้องการจริงๆฉันไม่รู้ว่าทำไมคำตอบนี้ไม่ใช่คำตอบที่ทำเครื่องหมายไว้!
James Poulose

1
@JamesPoulose เพราะฟังก์ชั่นนี้ล่าสุดดังนั้นคำตอบของฉันคือใหม่
Boiethios

53

หากคุณทราบทุกประเภทล่วงหน้าคุณสามารถใช้คุณลักษณะเพื่อเพิ่มtype_ofวิธีการ:

trait TypeInfo {
    fn type_of(&self) -> &'static str;
}

impl TypeInfo for i32 {
    fn type_of(&self) -> &'static str {
        "i32"
    }
}

impl TypeInfo for i64 {
    fn type_of(&self) -> &'static str {
        "i64"
    }
}

//...

ไม่มี Intrisics หรือ nothin 'ดังนั้นแม้จะมีข้อ จำกัด มากขึ้นนี่เป็นทางออกเดียวที่ทำให้คุณได้รับสตริงและมีเสถียรภาพ (ดูคำตอบของ French Boiethios ) อย่างไรก็ตามมันลำบากมากและไม่ได้คำนึงถึงพารามิเตอร์ประเภทดังนั้นเราจึงสามารถ ...

trait TypeInfo {
    fn type_name() -> String;
    fn type_of(&self) -> String;
}

macro_rules! impl_type_info {
    ($($name:ident$(<$($T:ident),+>)*),*) => {
        $(impl_type_info_single!($name$(<$($T),*>)*);)*
    };
}

macro_rules! mut_if {
    ($name:ident = $value:expr, $($any:expr)+) => (let mut $name = $value;);
    ($name:ident = $value:expr,) => (let $name = $value;);
}

macro_rules! impl_type_info_single {
    ($name:ident$(<$($T:ident),+>)*) => {
        impl$(<$($T: TypeInfo),*>)* TypeInfo for $name$(<$($T),*>)* {
            fn type_name() -> String {
                mut_if!(res = String::from(stringify!($name)), $($($T)*)*);
                $(
                    res.push('<');
                    $(
                        res.push_str(&$T::type_name());
                        res.push(',');
                    )*
                    res.pop();
                    res.push('>');
                )*
                res
            }
            fn type_of(&self) -> String {
                $name$(::<$($T),*>)*::type_name()
            }
        }
    }
}

impl<'a, T: TypeInfo + ?Sized> TypeInfo for &'a T {
    fn type_name() -> String {
        let mut res = String::from("&");
        res.push_str(&T::type_name());
        res
    }
    fn type_of(&self) -> String {
        <&T>::type_name()
    }
}

impl<'a, T: TypeInfo + ?Sized> TypeInfo for &'a mut T {
    fn type_name() -> String {
        let mut res = String::from("&mut ");
        res.push_str(&T::type_name());
        res
    }
    fn type_of(&self) -> String {
        <&mut T>::type_name()
    }
}

macro_rules! type_of {
    ($x:expr) => { (&$x).type_of() };
}

มาใช้กัน:

impl_type_info!(i32, i64, f32, f64, str, String, Vec<T>, Result<T,S>)

fn main() {
    println!("{}", type_of!(1));
    println!("{}", type_of!(&1));
    println!("{}", type_of!(&&1));
    println!("{}", type_of!(&mut 1));
    println!("{}", type_of!(&&mut 1));
    println!("{}", type_of!(&mut &1));
    println!("{}", type_of!(1.0));
    println!("{}", type_of!("abc"));
    println!("{}", type_of!(&"abc"));
    println!("{}", type_of!(String::from("abc")));
    println!("{}", type_of!(vec![1,2,3]));

    println!("{}", <Result<String,i64>>::type_name());
    println!("{}", <&i32>::type_name());
    println!("{}", <&str>::type_name());
}

เอาท์พุท:

i32
&i32
&&i32
&mut i32
&&mut i32
&mut &i32
f64
&str
&&str
String
Vec<i32>
Result<String,i64>
&i32
&str

สนามเด็กเล่นสนิม


คำตอบนี้อาจแบ่งออกเป็นสองคำตอบแยกกันเพื่อหลีกเลี่ยงการผสมสองคำตอบ
Prajwal Dhatwalia

2
@ PrajwalDhatwalia ฉันคิดเกี่ยวกับสิ่งที่คุณพูดและฉันรู้สึกว่าฉันพอใจกับวิธีที่รุ่นต่าง ๆ เติมเต็มซึ่งกันและกัน รุ่นคุณลักษณะแสดงความเรียบง่ายของสิ่งที่เวอร์ชันแมโครกำลังทำอยู่ภายใต้ประทุนทำให้เป้าหมายชัดเจนยิ่งขึ้น ในทางกลับกันเวอร์ชันแมโครจะแสดงวิธีทำให้เวอร์ชันคุณลักษณะโดยทั่วไปสามารถใช้งานได้มากขึ้น มันไม่ใช่วิธีเดียวที่จะทำ แต่ถึงแม้จะแสดงให้เห็นว่าเป็นไปได้ก็มีข้อได้เปรียบ โดยสรุปนี่อาจเป็นสองคำตอบ แต่ฉันรู้สึกว่าทั้งหมดมากกว่าผลรวมของส่วนต่างๆ
phicr

19

UPDรายการต่อไปนี้ใช้ไม่ได้อีกต่อไป ตรวจสอบคำตอบของ Shubhamเพื่อแก้ไข

std::intrinsics::get_tydesc<T>()ตรวจสอบ ตอนนี้อยู่ในสถานะ "ทดลอง" แต่ก็โอเคถ้าคุณเพิ่งแฮ็คระบบประเภท

ลองดูตัวอย่างต่อไปนี้:

fn print_type_of<T>(_: &T) -> () {
    let type_name =
        unsafe {
            (*std::intrinsics::get_tydesc::<T>()).name
        };
    println!("{}", type_name);
}

fn main() -> () {
    let mut my_number = 32.90;
    print_type_of(&my_number);       // prints "f64"
    print_type_of(&(vec!(1, 2, 4))); // prints "collections::vec::Vec<int>"
}

นี่คือสิ่งที่ใช้เป็นการภายในเพื่อนำ{:?}formatter ที่มีชื่อเสียงมาใช้


15

** อัพเดท ** สิ่งนี้ยังไม่ได้รับการยืนยันว่าทำงานได้ตลอดเวลาเมื่อเร็ว ๆ นี้

ฉันรวบรวมลังเล็กน้อยเพื่อทำสิ่งนี้ตามคำตอบของ vbo มันให้แมโครเพื่อส่งคืนหรือพิมพ์ชนิด

ใส่สิ่งนี้ในไฟล์ Cargo.toml ของคุณ:

[dependencies]
t_bang = "0.1.2"

จากนั้นคุณสามารถใช้มันได้เช่น:

#[macro_use] extern crate t_bang;
use t_bang::*;

fn main() {
  let x = 5;
  let x_type = t!(x);
  println!("{:?}", x_type);  // prints out: "i32"
  pt!(x);                    // prints out: "i32"
  pt!(5);                    // prints out: "i32"
}

@vbo กล่าวว่าวิธีแก้ปัญหาของเขาไม่ทำงานอีกต่อไป คุณทำงานไหม
Antony Hatchkins

ไม่ทำงาน `ข้อผิดพลาด [E0554]: #![feature]อาจไม่สามารถใช้กับช่องทางที่เสถียร`
Muhammed Moussa

7

println!("{:?}", var)นอกจากนี้คุณยังสามารถใช้วิธีการที่เรียบง่ายของการใช้ตัวแปรใน หากDebugไม่ได้ใช้งานสำหรับประเภทคุณสามารถดูประเภทในข้อความแสดงข้อผิดพลาดของคอมไพเลอร์:

mod some {
    pub struct SomeType;
}

fn main() {
    let unknown_var = some::SomeType;
    println!("{:?}", unknown_var);
}

( บทกวี )

มันสกปรก แต่ก็ใช้งานได้


8
หากDebugไม่ได้ใช้งาน - นี่เป็นกรณีที่ไม่น่าเป็นไปได้ หนึ่งในสิ่งแรกที่คุณควรทำมากที่สุดใด ๆ struct #[derive(Debug)]คือการเพิ่ม ฉันคิดว่าเวลาที่คุณไม่ต้องการDebugมีขนาดเล็กมาก
Shepmaster

1
คุณช่วยอธิบายสิ่งที่เกิดขึ้นได้println!("{:?}", unknown_var);ไหม? มันคือการแก้ไขสตริง แต่ทำไม:?ภายในวงเล็บปีกกา? @DenisKolodin
Julio Marins

ฉันก่อให้เกิดข้อผิดพลาด แนวคิดที่จะให้คอมไพเลอร์ให้ข้อมูลประเภทที่มีข้อผิดพลาด ฉันใช้Debugเพราะไม่ได้ใช้ แต่คุณสามารถใช้{}เช่นกัน
DenisKolodin

4

มี @ChrisMorgan คำตอบที่จะได้รับประเภทโดยประมาณ ("ลอย") ในการเกิดสนิมที่มั่นคงและมี @ShubhamJain คำตอบที่จะได้รับประเภทที่แม่นยำ ("f64") ผ่านฟังก์ชั่นที่ไม่เสถียรในยามค่ำคืนสนิม

ต่อไปนี้เป็นวิธีที่สามารถรับชนิดที่แม่นยำ (เช่นตัดสินใจระหว่าง f32 และ f64) ในการเกิดสนิมที่เสถียร:

fn main() {
    let a = 5.;
    let _: () = unsafe { std::mem::transmute(a) };
}

ผลลัพธ์ใน

error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
 --> main.rs:3:27
  |
3 |     let _: () = unsafe { std::mem::transmute(a) };
  |                           ^^^^^^^^^^^^^^^^^^^
  |
  = note: source type: `f64` (64 bits)
  = note: target type: `()` (0 bits)

ปรับปรุง

ความแปรปรวนปั่นป่วน

fn main() {
    let a = 5.;
    unsafe { std::mem::transmute::<_, ()>(a) }
}

สั้นกว่าเล็กน้อย แต่อ่านได้น้อยลง


หากคุณรู้อยู่แล้วว่าสามารถfloatบอกได้ระหว่างf32และf64สามารถทำได้ด้วยstd::mem::size_of_val(&a)
Antony Hatchkins

1

บางคำตอบอื่น ๆ ไม่ได้ทำงาน แต่ผมพบว่าtypenameงานลัง

  1. สร้างโครงการใหม่:

    cargo new test_typename
  2. ปรับเปลี่ยน Cargo.toml

    [dependencies]
    typename = "0.1.1"
  3. แก้ไขซอร์สโค้ดของคุณ

    use typename::TypeName;
    
    fn main() {
        assert_eq!(String::type_name(), "std::string::String");
        assert_eq!(Vec::<i32>::type_name(), "std::vec::Vec<i32>");
        assert_eq!([0, 1, 2].type_name_of(), "[i32; 3]");
    
        let a = 65u8;
        let b = b'A';
        let c = 65;
        let d = 65i8;
        let e = 65i32;
        let f = 65u32;
    
        let arr = [1,2,3,4,5];
        let first = arr[0];
    
        println!("type of a 65u8  {} is {}", a, a.type_name_of());
        println!("type of b b'A'  {} is {}", b, b.type_name_of());
        println!("type of c 65    {} is {}", c, c.type_name_of());
        println!("type of d 65i8  {} is {}", d, d.type_name_of());
        println!("type of e 65i32 {} is {}", e, e.type_name_of());
        println!("type of f 65u32 {} is {}", f, f.type_name_of());
    
        println!("type of arr {:?} is {}", arr, arr.type_name_of());
        println!("type of first {} is {}", first, first.type_name_of());
    }

ผลลัพธ์คือ:

type of a 65u8  65 is u8
type of b b'A'  65 is u8
type of c 65    65 is i32
type of d 65i8  65 is i8
type of e 65i32 65 is i32
type of f 65u32 65 is u32
type of arr [1, 2, 3, 4, 5] is [i32; 5]
type of first 1 is i32

ฉันได้ทำตามขั้นตอนที่คุณอธิบายแล้ว ณ วันนี้typenameไม่ทำงานกับตัวแปรโดยไม่ต้องพิมพ์อย่างชัดเจนในการประกาศ การรันด้วยmy_number จากคำถามให้ข้อผิดพลาดต่อไปนี้ "ไม่สามารถเรียกวิธีการtype_name_ofในประเภทตัวเลขที่ไม่ชัดเจน{float}ความช่วยเหลือ: คุณต้องระบุประเภทสำหรับการรวมนี้เช่นf32"
Antony Hatchkins

การทดสอบผม และทำงานได้ดี:0.65 type of c 0.65 0.65 is f64นี่คือรุ่นของฉัน:rustc 1.38.0-nightly (69656fa4c 2019-07-13)
Flyq

1

หากคุณเพียงต้องการทราบประเภทของตัวแปรของคุณในระหว่างการพัฒนาเชิงโต้ตอบฉันขอแนะนำให้ใช้rls (เซิร์ฟเวอร์ภาษาสนิม) ในตัวแก้ไขหรือ IDE ของคุณ จากนั้นคุณสามารถเปิดใช้งานหรือสลับความสามารถของโฮเวอร์อย่างถาวรและวางเคอร์เซอร์ไว้เหนือตัวแปร ไดอะล็อกเล็ก ๆ น้อย ๆ ควรมีข้อมูลเกี่ยวกับตัวแปรรวมถึงประเภท

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.