2015-05-28 14 views
15

Ich bin neu in Rust. Wie würde ich eine String von einer Rust-Funktion zurückgeben, die in Python verwendet werden kann?Rückgabe eines Strings von der Rust-Funktion an Python

Hier ist meine Rust Umsetzung:

use std::ffi::CString; 

#[no_mangle] 
pub extern fn query() -> CString { 
    let s = CString::new("Hello!").unwrap(); 
    return s; 
} 

Und der Python-Code, der es nennt:

from ctypes import cdll, c_char_p 

lib = cdll.LoadLibrary("target/release/libtest.so") 
result = lib.query() 

print(c_char_p(result).value) 

ich einen Segmentation Fault, wenn sein Lauf bekommen.

EDIT: Mit Vladimir Matveev des Rust-Code unten ich konnte es mit den Änderungen an meinem Python-Code zu arbeiten:

from ctypes import * 

lib = cdll.LoadLibrary("target/release/libtest.so") 
lib.query.restype = c_char_p 
result = lib.query() 
print cast(result, c_char_p).value 
lib.free_query(result) 
+0

Bitte überprüfen Sie http: // stackoverflow.com/questions/30440068/segmentierung-fault-wenn-rust-lib-mit-ruby-ffi und http://stackoverflow.com/questions/30312885/pass-python-list-to-embedded-rust- Funktion/30313295 # 30313295 und lassen Sie uns wissen, wie sich Ihre Frage unterscheidet. – Shepmaster

+0

Ich habe beide Fragen überprüft, und sie unterscheiden sich. In der ersten kommt der Aufruf von Ruby und meine Frage stammt von Python. In der zweiten Frage ist der Rückgabewert eine ganze Zahl, was ein einfacher Fall ist. Hier ist die Rückgabe speziell ein String-Wert. – LeeMobile

+1

Es gibt absolut nichts anderes auf der Rust-Seite, die sich je nach der Sprache, aus der Sie anrufen, ändern sollte. Soweit der Rust-Code betroffen ist, ruft C ihn an. Jede andere Sprache ruft etwas an, das wie C-Code aussieht. – Shepmaster

Antwort

11

Die direkteste Version wäre dies:

use libc::c_char; 
use std::ffi::CString; 
use std::mem; 

#[no_mangle] 
pub extern fn query() -> *mut c_char { 
    let s = CString::new("Hello!").unwrap(); 
    s.into_raw() 
} 

Hier kehren wir einen Zeiger auf eine Null-terminierte Sequenz von char s, die an Pythons c_char_p übergeben werden kann. Sie können nicht nur CString zurückgeben, weil es eine Rust-Struktur ist, die nicht direkt in C-Code verwendet werden soll - sie enthält Vec<u8> und besteht eigentlich aus drei zeigergroßen Ganzzahlen. Es ist nicht kompatibel mit char* von C direkt. Wir müssen einen rohen Zeiger daraus erhalten. CString::into_raw() Methode tut dies - es verbraucht die CString nach Wert, "vergisst", damit seine Zuordnung nicht zerstört wird, und gibt einen *mut c_char Zeiger an den Anfang des Arrays zurück.

Auf diese Weise wird die Zeichenfolge jedoch durchgesickert, weil wir ihre Zuordnung auf der Rust-Seite vergessen haben, und sie wird niemals freigegeben werden. Ich kenne Pythons FFI nicht genug, aber der direkteste Weg, um dieses Problem zu lösen, besteht darin, zwei Funktionen zu erstellen, eine zum Erzeugen der Daten und eine zum Freigeben. Dann müssen Sie die Daten von Python Seite frei durch diese befreiende Funktion aufrufen:

// above function 
#[no_mangle] 
pub extern fn query() -> *mut c_char { ... } 

#[no_mangle] 
pub extern fn free_query(c: *mut c_char) { 
    // convert the pointer back to `CString` 
    // it will be automatically dropped immediately 
    unsafe { CString::from_raw(c); } 
} 

CString::from_raw() Methode akzeptiert einen *mut c_char Zeiger und schafft eine CString Instanz aus ihm heraus, die Berechnung der Länge des zugrunde liegenden Null-terminierten String in der verarbeiten. Diese Operation impliziert Eigentumsübertragung, sodass der resultierende CString-Wert die Zuweisung besitzt und wenn sie gelöscht wird, wird die Zuweisung freigegeben. Genau das wollen wir.

+0

Ich musste die ersten zwei Schritte von [hier] (https://github.com/rust-lang/libc#usage) machen, um es zum Laufen zu bringen: (1) füge Abhängigkeit zu rustc in 'Cargo.toml' hinzu und (2) importieren Sie es in die Rost-Datei, wo es verwendet wird. (Ich bin mir nicht sicher, ob das immer notwendig ist, ich bin völlig neu in Sachen Rost.) – ArneHugo

1

Das Problem hierbei ist, dass Sie eine CString direkt zurückgeben, die nicht entsprechen der Darstellung einer Zeichenkette in C (Sie können here den Quellcode von CString sehen).

Sie sollten einen Zeiger auf die Zeichenfolge zurückgeben, indem Sie s.as_ptr() verwenden. Sie müssen jedoch sicherstellen, dass die Zuweisung der Zeichenfolge am Ende der Funktion nicht aufgehoben wird, da dies zu einem ungeordneten Zeiger führen würde.

Die einzige Lösung, die ich mir vorstellen kann, ist, forget zu verwenden, um Rost die Variable vergessen zu lassen, anstatt sie zu befreien. Natürlich müssen Sie einen Weg finden, um die Zeichenfolge später freizugeben, um ein Speicherleck zu vermeiden (siehe Vladimirs Antwort).

Mit den Änderungen, die ich erwähnt, sollte Ihr Rust Code wie folgt sein:

use std::ffi::CString; 
use std::mem; 

#[no_mangle] 
pub extern fn query() -> *const i8 { 
    let s = CString::new("Hello!").unwrap(); 
    let ptr = s.as_ptr(); 
    mem::forget(s); 
    return ptr; 
} 
Verwandte Themen