web-dev-qa-db-fra.com

Comment convertir des types numériques en toute sécurité et de manière idiomatique?

Note de l'éditeur: cette question provient d'une version de Rust antérieure à 1.0 et fait référence à certains éléments qui ne sont pas présents dans Rust 1.0. Les réponses contiennent toujours des informations précieuses .

Quelle est la manière idiomatique de convertir (disons) un usize en un u32?

Par exemple, le casting utilisant 4294967295us as u32 Fonctionne et les documents de référence Rust 0.12 sur le casting de type say

Une valeur numérique peut être convertie en n'importe quel type numérique. Une valeur de pointeur brut peut être convertie vers ou depuis n'importe quel type intégral ou type de pointeur brut. Toute autre distribution n'est pas prise en charge et ne pourra pas être compilée.

mais 4294967296us as u32 débordera silencieusement et donnera un résultat de 0.

J'ai trouvé ToPrimitive et FromPrimitive qui fournissent des fonctions agréables comme to_u32() -> Option<u32>, mais elles sont marquées comme instables:

#[unstable(feature = "core", reason = "trait is likely to be removed")]

Quelle est la façon idiomatique (et sûre) de convertir entre les types numériques (et pointeurs)?

La taille dépendante de la plate-forme de isize/usize est l'une des raisons pour lesquelles je pose cette question - le scénario d'origine était que je voulais convertir de u32 En usize afin que je puisse représenter un arbre dans un Vec<u32> (par exemple let t = Vec![0u32, 0u32, 1u32], puis pour obtenir le grand-parent du nœud 2 serait t[t[2us] as usize]), et je me suis demandé comment cela échouerait si usize était inférieur à 32 bits.

35
Caspar

Sur ToPrimitive/FromPrimitive

RFC 369, Num Reform, déclare :

Idéalement, [...] ToPrimitive [...] seraient tous supprimés au profit d'une manière plus raisonnée de travailler avec des énumérations de type C

En attendant, ces traits vivent dans le num crate :

Traiter sans les traits

D'un type qui s'intègre complètement dans un autre

Il n'y a aucun problème ici. Utilisez From pour être clair qu'aucune perte ne se produit:

fn example(v: i8) -> i32 {
    i32::from(v) // or v.into()
}

Vous pouvez choisir d'utiliser as, mais il est recommandé de l'éviter lorsque vous n'en avez pas besoin (voir ci-dessous):

fn example(v: i8) -> i32 {
    v as i32
}

D'un type qui ne rentre pas complètement dans un autre

Il n'y a pas une seule méthode qui ait un sens général - vous demandez comment placer deux choses dans un espace destiné à une seule. Une bonne première tentative consiste à utiliser un Option - Some lorsque la valeur convient et None sinon. Vous pouvez ensuite échouer votre programme ou remplacer une valeur par défaut, selon vos besoins.

Depuis Rust 1.34, vous pouvez utiliser TryFrom :

use std::convert::TryFrom;

fn example(v: i32) -> Option<i8> {
    i8::try_from(v).ok()
}

Avant cela, vous devez écrire vous-même un code similaire:

fn example(v: i32) -> Option<i8> {
    if v > std::i8::MAX as i32 {
        None
    } else {
        Some(v as i8)
    }
}

Que fait as

mais 4294967296us as u32 débordera silencieusement et donnera un résultat de 0

Lors de la conversion en un type plus petit, as prend simplement les bits inférieurs du nombre, sans tenir compte des bits supérieurs, y compris le signe:

fn main() {
    let a: u16 = 0x1234;
    let b: u8 = a as u8;
    println!("0x{:04x}, 0x{:02x}", a, b); // 0x1234, 0x34

    let a: i16 = -257;
    let b: u8 = a as u8;
    println!("0x{:02x}, 0x{:02x}", a, b); // 0xfeff, 0xff
}
22
Shepmaster