Warum wird davon abgeraten, einen Verweis auf einen String (& String), Vec (& Vec) oder Box (& Box) als Funktionsargument zu akzeptieren?


126

Ich habe einen Rust-Code geschrieben, der &Stringein Argument verwendet:

fn awesome_greeting(name: &String) {
    println!("Wow, you are awesome, {}!", name);
}

Ich habe auch Code geschrieben, der einen Verweis auf ein Vecoder enthält Box:

fn total_price(prices: &Vec<i32>) -> i32 {
    prices.iter().sum()
}

fn is_even(value: &Box<i32>) -> bool {
    **value % 2 == 0
}

Ich erhielt jedoch einige Rückmeldungen, dass es keine gute Idee ist, dies so zu tun. Warum nicht?

Antworten:


161

TL; DR: Man kann stattdessen verwenden &str, &[T]oder &Tfür generischen Code zu ermöglichen.


  1. Einer der Hauptgründe für die Verwendung von a Stringoder a Vecist, dass sie das Erhöhen oder Verringern der Kapazität ermöglichen. Wenn Sie jedoch eine unveränderliche Referenz akzeptieren, können Sie keine dieser interessanten Methoden für das Vecoder verwenden String.

  2. Akzeptieren einer &String, &Vecoder &Boxauch erfordert das Argument auf dem Heap zugewiesen werden , bevor Sie die Funktion aufrufen kann. Das Akzeptieren von a &strermöglicht ein Zeichenfolgenliteral (in den Programmdaten gespeichert) und das Akzeptieren von a &[T]oder &Termöglicht ein vom Stapel zugewiesenes Array oder eine Variable. Eine unnötige Zuordnung ist ein Leistungsverlust. Dies wird normalerweise sofort angezeigt, wenn Sie versuchen, diese Methoden in einem Test oder einer mainMethode aufzurufen :

    awesome_greeting(&String::from("Anna"));
    total_price(&vec![42, 13, 1337])
    is_even(&Box::new(42))
  3. Eine weitere Leistung Überlegung ist , dass &String, &Vecund &Boxeine unnötige Schicht Indirektionsebene vorstellen , wie Sie das dereferenzieren haben &Stringeine zu bekommen Stringund dann eine zweite dereferenzieren ausführen , um am Ende an &str.

Stattdessen sollten Sie ein String-Slice ( &str), ein Slice ( &[T]) oder nur eine Referenz ( &T) akzeptieren . A &String, &Vec<T>oder &Box<T>wird automatisch in einen dazu gezwungen werden &str, &[T]oder &T, respectively.

fn awesome_greeting(name: &str) {
    println!("Wow, you are awesome, {}!", name);
}
fn total_price(prices: &[i32]) -> i32 {
    prices.iter().sum()
}
fn is_even(value: &i32) -> bool {
    *value % 2 == 0
}

Jetzt können Sie diese Methoden mit einem breiteren Satz von Typen aufrufen. Zum Beispiel awesome_greetingkann mit einem String - Literal (genannt werden "Anna") oder einem zugeordneten String. total_pricekann mit einem Verweis auf ein Array ( &[1, 2, 3]) oder ein zugewiesenes aufgerufen werden Vec.


Wenn Sie Elemente zum Stringoder hinzufügen oder entfernen möchten Vec<T>, können Sie eine veränderbare Referenz ( &mut Stringoder &mut Vec<T>) verwenden:

fn add_greeting_target(greeting: &mut String) {
    greeting.push_str("world!");
}
fn add_candy_prices(prices: &mut Vec<i32>) {
    prices.push(5);
    prices.push(25);
}

Speziell für Slices können Sie auch ein &mut [T]oder akzeptieren &mut str. Auf diese Weise können Sie einen bestimmten Wert innerhalb des Slice mutieren, aber Sie können die Anzahl der Elemente innerhalb des Slice nicht ändern (was bedeutet, dass dies für Zeichenfolgen sehr eingeschränkt ist):

fn reset_first_price(prices: &mut [i32]) {
    prices[0] = 0;
}
fn lowercase_first_ascii_character(s: &mut str) {
    if let Some(f) = s.get_mut(0..1) {
        f.make_ascii_lowercase();
    }
}

5
Wie wäre es mit einem tl; dr am Anfang? Diese Antwort ist schon etwas lang. Etwas wie " &strist allgemeiner (wie in: weniger Einschränkungen auferlegt) ohne eingeschränkte Fähigkeiten"? Außerdem: Punkt 3 ist meiner Meinung nach oft nicht so wichtig. Normalerweise leben Vecs und Strings auf dem Stapel und oft sogar irgendwo in der Nähe des aktuellen Stapelrahmens. Der Stack ist normalerweise heiß und die Dereferenzierung wird aus einem CPU-Cache bereitgestellt.
Lukas Kalbertodt

3
@Shepmaster: In Bezug auf die Zuweisungskosten kann es sinnvoll sein, das besondere Problem der Teilzeichenfolgen / Slices zu erwähnen, wenn über die obligatorische Zuweisung gesprochen wird. total_price(&prices[0..4])erfordert keine Zuweisung eines neuen Vektors für das Slice.
Matthieu M.

4
Dies ist eine großartige Antwort. Ich habe gerade in Rust angefangen und war gerade dabei herauszufinden, wann ich ein &strund warum verwenden sollte (ich komme aus Python, daher beschäftige ich mich normalerweise nicht explizit mit Typen). Alles perfekt
geklärt

2
Tolle Tipps zu Parametern. Benötigen Sie nur einen Zweifel: "Das Akzeptieren eines & String, & Vec oder & Box erfordert auch eine Zuordnung, bevor Sie die Methode aufrufen können." ... Warum ist das so? Könnten Sie bitte auf den Teil in den Dokumenten hinweisen, in dem ich dies ausführlich lesen kann? (Ich bin ein Anfänger). Können wir ähnliche Tipps zu den Rückgabetypen haben?
Nawaz

2
Mir fehlen Informationen darüber, warum eine zusätzliche Zuordnung erforderlich ist. String wird auf dem Heap gespeichert, wenn & String als Argument akzeptiert wird. Warum übergibt Rust nicht einfach einen auf dem Stapel gespeicherten Zeiger, der auf den Heap-Speicherplatz verweist? Ich verstehe nicht, warum das Übergeben eines & String eine zusätzliche Zuordnung erfordern würde, indem ein String übergeben wird Slice sollte auch das Senden eines auf dem Stapel gespeicherten Zeigers erfordern, der auf den Heap-Speicherplatz zeigt.
Cjohansson

22

Neben Shepmaster Antwort , zu einem anderen Grund zu akzeptieren ein &str(und ähnlich &[T]usw.) wegen all der anderen Arten ist neben String und &strdass auch erfüllen Deref<Target = str>. Eines der bemerkenswertesten Beispiele ist Cow<str>, dass Sie sehr flexibel entscheiden können, ob Sie mit eigenen oder geliehenen Daten arbeiten.

Wenn Sie haben:

fn awesome_greeting(name: &String) {
    println!("Wow, you are awesome, {}!", name);
}

Aber Sie müssen es mit a nennen Cow<str>, Sie müssen dies tun:

let c: Cow<str> = Cow::from("hello");
// Allocate an owned String from a str reference and then makes a reference to it anyway!
awesome_greeting(&c.to_string());

Wenn Sie den Argumenttyp in ändern &str, können Sie ihn Cownahtlos und ohne unnötige Zuordnung verwenden, genau wie bei String:

let c: Cow<str> = Cow::from("hello");
// Just pass the same reference along
awesome_greeting(&c);

let c: Cow<str> = Cow::from(String::from("hello"));
// Pass a reference to the owned string that you already have
awesome_greeting(&c);

Durch das Akzeptieren wird &strdas Aufrufen Ihrer Funktion einheitlicher und bequemer, und der "einfachste" Weg ist jetzt auch der effizienteste. Diese Beispiele funktionieren auch mit Cow<[T]>usw.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.