Como imprimo o tipo de uma variável no Rust?

239

Eu tenho o seguinte:

let mut my_number = 32.90;

Como imprimo o tipo de my_number ?

Usando typee type_ofnão funcionou. Existe outra maneira de imprimir o tipo do número?

user2431012
fonte

Respostas:

177

Se você apenas deseja descobrir o tipo de uma variável e está disposto a fazê-lo em tempo de compilação, pode causar um erro e solicitar que o compilador a escolha.

Por exemplo, defina a variável para um tipo que não funcione :

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}`

Ou chame um método inválido :

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();
  |               ^^^^^^^^^^^^

Ou acesse um campo inválido :

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
  |               ^^^^^^^^^^^^

Eles revelam o tipo, que neste caso não é totalmente resolvido. É chamado de "variável de ponto flutuante" no primeiro exemplo e " {float}" nos três exemplos; esse é um tipo parcialmente resolvido que pode terminar f32ou f64, dependendo de como você o usa. " {float}" Não é um nome de tipo legal, é um espaço reservado que significa "Não tenho muita certeza do que é isso", mas é um número de ponto flutuante. No caso de variáveis ​​de ponto flutuante, se você não o restringir, o padrão será f64¹. (Um literal inteiro não qualificado será padronizado como i32.)

Veja também:


Still Ainda pode haver maneiras de confundir o compilador para que ele não possa decidir entre f32e f64; Não tenho certeza. Costumava ser tão simples quanto 32.90.eq(&32.90), mas isso trata tanto como f64agora e é feliz, então eu não sei.

Chris Morgan
fonte
4
:?já há muito tempo foi implementado manualmente. Mais importante, porém, a std::fmt::Debugimplementação (para isso é o que :?usa) para tipos de número não inclui mais um sufixo para indicar de que tipo ele é.
Chris
2
Eu uso muito essas técnicas para tentar encontrar o tipo de uma expressão, mas nem sempre funciona, especialmente quando há parâmetros de tipo envolvidos. O compilador, por exemplo, me diz que espera um ImageBuffer<_, Vec<_>>que não me ajude muito quando estou tentando escrever uma função que usa uma dessas coisas como parâmetro. E isso acontece no código que de outra forma compila até eu adicionar o :(). Não existe melhor maneira?
Christopher Armstrong
2
Isso parece ser um pouco complicado e pouco intuitivo. Seria muito difícil para o editor de código, por exemplo, o Emacs fornecer o tipo quando o cursor estiver na variável, como em muitos outros idiomas? Se o compilador puder dizer o tipo com erro, certamente já deve saber o tipo quando não houver nenhum erro?
Xji
1
@JIXiang: o Rust Language Server tem o objetivo de fornecer essas informações a um IDE, mas ainda não está maduro - seu primeiro lançamento alfa ocorreu apenas alguns dias atrás. Sim, esta é uma abordagem complexa; sim, maneiras menos esotéricas de alcançar a meta estão sempre chegando.
Chris Morgan
1
isso parece muito com um hack. essa é realmente a maneira idiomática de verificar o tipo de uma variável?
confused00
109

Há uma função instável std::intrinsics::type_nameque pode lhe dar o nome de um tipo, embora você precise usar uma compilação noturna do Rust (é improvável que isso funcione no Rust estável). Aqui está um exemplo:

#![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"
}
Shubham Jain
fonte
@ vbo: não até que esteja estabilizado. É improvável que algo assim fique estabilizado por algum tempo, se alguma vez - e não me surpreenderia se nunca estiver estabilizado; não é o tipo de coisa que você realmente deveria fazer.
Chris Morgan
2
Em rust-nightly (1.3), ele só funcionou ao mudar a primeira linha para#![feature(core_intrinsics)]
AT
1
@DmitriNesteruk: print_type_ofestá usando referências ( &T), não valores ( T), então você deve passar &&strao invés de &str; isto é, print_type_of(&"foo")e não print_type_of("foo").
Chris Morgan
6
std::any::type_nameé estável desde a ferrugem 1.38: stackoverflow.com/a/58119924
Tim Robinson
1
Obter o tipo de algo no compile / runtime possui casos de uso válidos. Para serialização, por exemplo - ou simplesmente para fins de depuração. Aqueles que escrevem "Você nunca deve fazer uma coisa dessas" simplesmente nunca se depararam com esses casos de uso ainda.
BitTickler 12/01
67

Você pode usar a std::any::type_namefunção Isso não precisa de um compilador noturno ou de uma caixa externa, e os resultados são bastante corretos:

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}}
}

Esteja avisado: como dito na documentação, essas informações devem ser usadas apenas para fins de depuração:

Isto é destinado ao uso em diagnóstico. O conteúdo e o formato exatos da sequência não são especificados, exceto uma descrição do tipo de melhor esforço.

Se você deseja que sua representação de tipo permaneça a mesma entre as versões do compilador, use uma característica, como na resposta do phicr .

Boiethios
fonte
1
melhor resposta para mim, como a maioria dos desenvolvedores deseja usar isso para fins de depuração, como impressão de falhas de análise
kaiser
Exatamente o que eu precisava, não sei por que essa não é a resposta marcada!
James Poulose
1
@ JamesPoulose Como essa função é recente, minha resposta é mais recente.
Boiethios
53

Se você conhece todos os tipos de antemão, pode usar características para adicionar um type_ofmétodo:

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"
    }
}

//...

Nada de intrísico ou nada, portanto, embora mais limitada, essa é a única solução que fornece uma string e é estável. (veja a resposta de French Boiethios ) No entanto, é muito trabalhoso e não leva em consideração os parâmetros de tipo, para que pudéssemos ...

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() };
}

Vamos usá-lo:

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());
}

resultado:

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

Rust Playground

phicr
fonte
Essa resposta pode ser dividida em duas respostas separadas para evitar misturar as duas.
Prajwal Dhatwalia 28/06/19
2
@PrajwalDhatwalia Estive pensando no que você disse e sinto que estou satisfeito com a forma como as versões se complementam. A versão de características mostra uma simplificação do que a versão macro está fazendo sob o capô, tornando seus objetivos mais claros. A versão macro, por outro lado, mostra como tornar a versão de característica mais geralmente utilizável; não é a única maneira de fazer isso, mas mostrar que é possível é vantajoso. Em resumo, podem ser duas respostas, mas acho que o todo é maior que a soma de suas partes.
Phicr 30/07/19
19

UPD O seguinte não funciona mais. Verifique a resposta de Shubham para correção.

Confira std::intrinsics::get_tydesc<T>(). Ele está no estado "experimental" no momento, mas não há problema se você estiver apenas invadindo o sistema de tipos.

Confira o seguinte exemplo:

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>"
}

É isso que é usado internamente para implementar o famoso {:?}formatador.

vbo
fonte
15

** ATUALIZAÇÃO ** Isso não foi verificado para funcionar recentemente.

Eu montei uma pequena caixa para fazer isso com base na resposta do vbo. Ele fornece uma macro para retornar ou imprimir o tipo.

Coloque isso no seu arquivo Cargo.toml:

[dependencies]
t_bang = "0.1.2"

Então você pode usá-lo assim:

#[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"
}
mpiccolo
fonte
@vbo diz que sua solução não funciona mais. O seu funciona?
Antony Hatchkins
não está funcionando `erro [E0554]: #![feature]não pode ser usado no canal de lançamento estável`
Muhammed Moussa
7

Você também pode usar a abordagem simples de utilizar a variável em println!("{:?}", var). Se Debugnão estiver implementado para o tipo, você poderá ver o tipo na mensagem de erro do compilador:

mod some {
    pub struct SomeType;
}

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

( cercadinho )

Está sujo, mas funciona.

DenisKolodin
fonte
8
Se Debugnão for implementado - esse é um caso bastante improvável. Uma das primeiras coisas que você deve fazer para qualquer estrutura é adicionar #[derive(Debug)]. Eu acho que os tempos em que você não quer Debugsão muito pequenos.
Shepmaster
1
você pode explicar o que está acontecendo println!("{:?}", unknown_var);? É uma interpolação de cordas, mas por que o :?interior dos colchetes? @DenisKolodin
Julio Marins
Eu provoco erro. A idéia de permitir que o compilador forneça informações de tipo com erro. Eu usei Debugporque não está implementado, mas você pode usá-lo {}também.
DenisKolodin
4

Existe uma resposta @ChrisMorgan para obter um tipo aproximado ("float") em ferrugem estável e há uma resposta @ShubhamJain para obter um tipo preciso ("f64") através da função instável em ferrugem noturna.

Agora, aqui está uma maneira de obter um tipo preciso (ou seja, decidir entre f32 e f64) com ferrugem estável:

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

resulta em

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)

Atualizar

A variação do peixe-boi

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

é um pouco mais curto, mas um pouco menos legível.

Antony Hatchkins
fonte
Se você sabe que é float, dizendo entre f32e f64pode ser realizado comstd::mem::size_of_val(&a)
Antony Hatchkins
1

Algumas outras respostas não funcionam, mas acho que a caixa de nomes de tipos funciona.

  1. Crie um novo projeto:

    cargo new test_typename
  2. Modifique o Cargo.toml

    [dependencies]
    typename = "0.1.1"
  3. Modifique seu código fonte

    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());
    }

A saída é:

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
Flyq
fonte
Eu segui os passos que você descreveu. A partir de hoje, typenamenão funciona com variáveis ​​sem tipo explícito na declaração. Executá-lo com my_number a partir da pergunta dá o seguinte erro "não pode chamar o método type_name_ofde tipo numérico ambígua {float}ajuda: você deve especificar um tipo para essa ligação, como. f32"
Antony Hatchkins
I teste 0.65e funciona bem: type of c 0.65 0.65 is f64. aqui está a minha versão:rustc 1.38.0-nightly (69656fa4c 2019-07-13)
Flyq 12/09/1919
1

Se você está apenas querendo saber o tipo de sua variável durante o desenvolvimento interativo, eu recomendo o uso de rls (rust language server) dentro do seu editor ou ide. Você pode simplesmente ativar ou alternar permanentemente a capacidade de passar o mouse e apenas colocar o cursor sobre a variável. Um pequeno diálogo deve apresentar informações sobre a variável, incluindo o tipo.

nrdxp
fonte