6
votes

Comment créer un format! renvoyer un & str à partir d'une expression conditionnelle?

Je suis tombé sur ce problème où format! crée une valeur temporaire dans un modèle qui n'est ancré à rien, pour autant que je sache.

error[E0716]: temporary value dropped while borrowed
 --> src/main.rs:5:24
  |
3 |     let category = match x {
  |         -------- borrow later stored here
4 |         0...9 => "Between 0 and 9",
5 |         number @ 10 => format!("It's a {}!", number).as_str(),
  |                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^        - temporary value is freed at the end of this statement
  |                        |
  |                        creates a temporary which is freed while still in use
  |
  = note: consider using a `let` binding to create a longer lived value
  = note: this error originates in a macro outside of the current crate (in Nightly builds, run with -Z external-macro-backtrace for more info)

Dans ce code, le type de catégorie est un & str , qui se satisfait en renvoyant un littéral comme "Entre 0 et 9" . Si je veux formater la valeur correspondante en une tranche en utilisant as_str () , alors j'obtiens une erreur:

let x = 42;
let category = match x {
    0...9 => "Between 0 and 9",
    number @ 10 => format!("It's a {}!", number).as_str(),
    _ if x < 0 => "Negative",
    _ => "Something else",
};

println!("{}", category);

J'ai fait quelques lectures, et j'ai trouvé des personnes avec des problèmes similaires, mais je ne semble pas trouver de solution.

Une solution de contournement simple serait que category soit une String au lieu d'un & str , mais je n'aime pas l'idée de devoir mettre .to_string () à la fin de chaque littéral du motif, car ce n'est pas aussi propre .

Existe-t-il un moyen de résoudre le problème ou dois-je simplement le contourner?


0 commentaires

3 Réponses :


7
votes

Il s'agit à 90% d'un doublon de Renvoyer la chaîne locale sous forme de tranche (& str) , voir cela pour plusieurs autres solutions .

Il y a une possibilité supplémentaire car tout cela est dans une seule fonction: vous pouvez déclarer une variable pour la String et ne la définir que lorsque vous avez besoin d'allouer. Le compilateur (obliquement) suggère ceci:

Pensez à utiliser une liaison let pour créer une valeur plus durable

fn main() {
    let x = 42;
    let tmp;

    let category = match x {
        0...9 => "Between 0 and 9",
        number @ 10 => {
            tmp = format!("It's a {}!", number);
            &tmp
        }
        _ if x < 0 => "Negative",
        _ => "Something else",
    };

    println!("{}", category);
}

C'est la même chose que d'utiliser un Cow , juste géré par le compilateur au lieu d'un type spécifique.

p >


0 commentaires

1
votes

format! ne peut pas renvoyer & str car il allouera toujours String . Ce qu'il est possible de faire est de renvoyer un & str à partir d'une String , ce que vous avez fait dans votre code.

Comme le compilateur l'a indiqué, le créé La chaîne est immédiatement supprimée après sa création car elle est sortie de la portée actuelle et un moyen de contourner pourrait être une variable externe qui n'est pas liée à la portée match . Par exemple:

use core::str;

fn each_digit<F>(mut number: u32, mut f: F)
where
    F: FnMut(u8),
{
    while number > 0 {
        f((number % 10) as u8);
        number /= 10;
    }
}

fn main() {
    const BUFFER_LEN: usize = 20;
    let mut buffer = [0u8; BUFFER_LEN];

    let x = 12344329;
    let category = match x {
        0...9 => "Between 0 and 9",
        number @ 123443219 => {
            let mut idx = BUFFER_LEN;
            each_digit(number, |digit| {
                let ascii = digit + 48;
                idx -= 1;
                buffer[idx] = ascii;
            });
            str::from_utf8(&buffer[idx..BUFFER_LEN]).unwrap()
        },
        _ => "Something else",
    };

    assert_eq!("123443219", category);
}

Si vous voulez un environnement [no_std] ou ne voulez pas faire d'allocation dynamique, vous pouvez jeter un œil à ceci extrait de code limité:

use std::fmt::Write;

fn main() {
    let mut buffer = String::with_capacity(20);
    buffer.push_str("It's a ");

    let x = 10;
    let category = match x {
        0...9 => "Between 0 and 9",
        number @ 10 => {
            write!(&mut buffer, "{}", number).unwrap();
            buffer.as_str()
        }
        _ if x < 0 => "Negative",
        _ => "Something else",
    };

    println!("{}", category);
}


2 commentaires

Vous pouvez utiliser write! (& Mut buffer, "{}", number) pour enregistrer une allocation + copie dans le premier extrait, et je pense que cela fonctionnera également sous no_std avec un tampon fixe (non testé).


Comment écrire un entier sous forme de chaîne dans un tableau d'octets avec no_std?



0
votes

Dans mon cas, Comment faire surmonter la "perte de valeur temporaire lors de l'emprunt" lors de la conversion d'un i32 en & str
Je pourrais le résoudre en déplaçant l'appel dans les branches

pub fn uidl(&mut self, message_number: Option<i32>) -> POP3Result {
    let command = match message_number {
        Some(_) => POP3Command::UidlOne,
        None => POP3Command::UidlAll,
    };

    match message_number {
        Some(i) => {
            // Here the value is not dropped because it is not leaving the scope
            self.execute_command(command, Some(arg.to_string().as_str()))
        }
        // Here I had to duplicate the call
        None => self.execute_command(command, None),
    }
}

Un peu ce qui est suggéré dans le message d'erreur https://doc.rust-lang.org/error-index.html#E0597


0 commentaires