C'è un modo per implementare Into <& str> o From <T> per & str?

Aug 29 2020

Stavo scrivendo un po 'di codice in Rust cercando di definire una CLI, usando il (altrimenti piuttosto ottimo) crate clap, e mi sono imbattuto in un problema piuttosto critico. Metodi di Appaccettare un Into<&'help str>, e non sono riuscito a trovare un modo per implementare questo tratto.

In effetti da quello che ho capito, è assolutamente inattuabile:

struct JustWorkDamnIt {
    string: String
}

impl From<JustWorkDamnIt> for &str {
    fn from(arg: JustWorkDamnIt) -> Self {
        return arg.string.as_str()
    }
}

... che produce:

error[E0515]: cannot return value referencing local data `arg.string`
  --> src/cmd/interactive.rs:25:16
   |
25 |         return arg.string.as_str()
   |                ----------^^^^^^^^^
   |                |
   |                returns a value referencing data owned by the current function
   |                `arg.string` is borrowed here

È interessante notare che, tuttavia, la restituzione di un valore letterale si compila bene (il che, a mio clapavviso, è il motivo per cui non dispiace usare questo tratto). Presumibilmente è perché il letterale è compilato in una regione statica di memoria e quindi non è di proprietà della funzione:

impl From<JustWorkDamnIt> for &str {
    fn from(arg: JustWorkDamnIt) -> Self {
        return "literal"
    }
}

Ma, voglio dire, sicuramente c'è un modo per implementare questo tratto e restituire stringhe dinamiche? Forse un uso intelligente di Box <> o qualcosa del genere, idk. Credo di aver provato tutte le cose a cui potevo pensare.

E se non c'è un modo, allora questo sembra un difetto piuttosto evidente per Rust - tratti che possono essere dichiarati e usati nelle intestazioni delle funzioni, ma non possono essere effettivamente implementati in modo significativo (non ha molto senso restituire un letterale). Se questo risulta essere il caso, creerò un problema sul repository rust-lang per questo flusso, ma prima volevo controllare la sanità mentale qui.


UPD: Grazie per i commenti, mi hanno fatto riflettere più a fondo su questo problema.

Il problema ha a che fare con le vite, a quanto pare. Mi scuso per non aver mostrato l'intero codice, ho pensato che gli snippet che ho condiviso avrebbero descritto il problema a sufficienza, ma col senno di poi ha senso che il contesto sarebbe importante con le vite di Rust in gioco.

Ho trovato una "soluzione" per questa particolare istanza del problema. Dal momento che il codice in questione verrà eseguito solo una volta per ogni avvio eseguibile, posso solo Box::leakl' &'static stre chiamare un giorno. Tuttavia, vorrei capire se esiste una soluzione più generale che potrebbe essere utilizzata per stringhe dinamiche create spesso.

impl Cmd for InteractiveCmd {
    fn build_args_parser<'a, 'b>(&'b self, builder: App<'a>) -> App<'a> {
        // leak() works here but i'm curious if there's a better way
        let staticStr : &'static str = Box::leak(Box::from(format!("Interactive {} shell", APPNAME).as_str()));
        let rv = builder
            // pub fn about<S: Into<&'b str>>(mut self, about: S) -> Self
            .about(staticStr) 
            .version("0.1");
        return rv;
    }
}

fn main() {
    let interactive = InteractiveCmd::new();
    let mut app = App::new(APPNAME)
        .version(APPVER)
        .author(AUTHORS)
      .subcommand(interactive.build_args_parser(App::new("interactive")));
}

Attualmente mi trovo di fronte a 2 punti di confusione qui:

  • Perché non c'è impl From<String> for &str? Il compilatore afferma che esiste per impl From<String> for &mut str, e non vedo l'importanza di mutqui.
  • ... e se c'è una buona ragione per non farlo impl From<String> for &str, avrebbe senso scoraggiare in qualche modo l'uso delle Into<&str>API pubbliche delle biblioteche?

O forse il problema è con la mia funzione build_args_parser e semplicemente non è un'opzione per scaricare il lavoro su di essa per quanto riguarda Rust?

Risposte

1 dianhenglau Aug 31 2020 at 00:28

Sembra che tu stia cercando di convertire un file Stringa &'a str. Puoi farlo in questo modo:

use clap::App;

fn main() {
    let s = format!("Interactive {} shell", "Testing");
    let app = App::new("Testing")
        .about(&*s); // or `.about(s.as_str());` it's the same
}

Ecco la firma della funzione che vogliamo chiamare:

pub fn about<S: Into<&'b str>>(self, about: S) -> Self

Quindi il aboutparametro deve implementare il tratto Into<&'b str>. Secondo std::convert::Into, sappiamo che &'b strimplementa il tratto Into<&'b str>. Quindi ora abbiamo solo bisogno di un modo per convertirci Stringin &'b str. Il std::string::Stringci dicono che ci sono due modi: uso sia s.as_str()o &*s.