Pureline Rust
Idiomatisches Rust: Ownership, Result, rustfmt.
Pureline Rust folgt **idiomatischem Rust**, statt Java-Konventionen in Rust-Syntax zu übersetzen. Ownership, Lifetimes, Enums, Pattern Matching und das Typsystem sind Kernstärken, keine Hindernisse, die man umgehen müsste.
Dieses Profil ist dokumentiert, wird vom Browser-Checker aber noch nicht automatisch geprüft.
Profilregeln
Idiomatische Namen und Sichtbarkeit
Typen, Traits und Enum-Varianten: PascalCase. Funktionen, Methoden, Module, Felder und lokale Variablen: snake_case. Konstanten und Statics: SCREAMING_SNAKE_CASE. Crates: sprechende snake_case-Namen (Cargo-Paketnamen dürfen Bindestriche enthalten).
Traits übernehmen **nicht** das Java-I-Prefix. Storage statt IStorage.
Items bleiben privat, solange kein Konsument größere Sichtbarkeit braucht; wo passend ist pub(crate) zu bevorzugen. use-Imports ersetzen wiederholte lange voll qualifizierte Pfade, außer wo explizite Qualifizierung eine Mehrdeutigkeit auflöst.
pub trait IStorage {
} pub(crate) trait Storage {
} Guard Clauses und linearer Fluss
Früh validieren und den erfolgreichen Pfad auf der Basis-Einrückung halten.
else nach einem Zweig, der bereits zurückkehrt, abbricht oder fortsetzt, entfällt. Ein if/else-**Ausdruck** ist erlaubt für echte Zwei-Wege-Wertauswahl, wenn er klarer ist als die Alternativen. let ... else ist ein **bevorzugtes Rust-Guard-Konstrukt** und kein Verstoß gegen das Avoid-else-Prinzip.
fn open_session(request: &Request) -> Result<Session, SessionError> {
if !request.is_valid() {
return Err(SessionError::InvalidRequest);
}
let user = request.user().ok_or(SessionError::MissingUser)?;
Session::open(user)
} let Some(session) = sessions.get(&id) else {
return Err(SessionError::NotFound);
};
session.start() `Result`, `Option` und `?` bevorzugen
Erwartete Fehler werden mit Result<T, E> dargestellt, Abwesenheit mit Option<T>. ? dient der geradlinigen Weitergabe, map_err/ok_or_else kommen zum Einsatz, wenn Fehlerkontext wirklich hilft.
match wird verwendet, wenn Ergebnisse unterschiedlich behandelt werden müssen; verschachtelte Combinator-Ketten werden vermieden. Result-Werte werden nicht stillschweigend verworfen: es wird entschieden, ob weitergereicht, geloggt, wiederhergestellt oder mit begründeter Erklärung bewusst ignoriert wird.
let _ = std::fs::write(path, data); fn load_config(path: &Path) -> Result<Config, ConfigError> {
let content = std::fs::read_to_string(path)?;
let config = toml::from_str(&content)?;
Ok(config)
} `unwrap`, `expect` und `panic!` einschränken
unwrap() oder expect() werden nie für gewöhnliche behebbare Produktionsfehler verwendet. Akzeptabel sind sie in Tests, Prototypen oder dokumentierten Invarianten, die wirklich nicht verletzt werden können.
panic! ist keine normale Fehlerbehandlungsstrategie. Explizite Behandlung ist verstecktem Versagen durch ungeprüfte Indizierung oder Konvertierung vorzuziehen.
let config = load_config(path).unwrap(); let config = load_config(path)?; `match` und Patterns für echte Zustandsverzweigung
Exhaustives match wird für Enums und bedeutungsvolle Alternativen verwendet; if let oder let ... else für einfache Einzel-Pattern-Fälle. match wird nicht auf gewöhnliche Boolean-Prüfungen gezwungen.
Breite _-Arme, die künftige Zustände still maskieren, werden vermieden, wenn das Aufzählen der Varianten klarer wäre.
match state {
SessionState::Ready => "Ready",
_ => "Other",
} fn label(state: SessionState) -> &'static str {
match state {
SessionState::Ready => "Ready",
SessionState::Closed => "Closed",
SessionState::Failed => "Failed",
}
} Ownership muss bewusst sein
Borrowing mit &T oder &mut T ist zu bevorzugen, wenn kein Ownership-Transfer nötig ist. Owned Values werden akzeptiert, wenn der Aufgerufene Ownership wirklich braucht.
Cloning wird minimiert; .clone() kommt zum Einsatz, wenn Kosten und Zweck klar sind. Überflüssige Arc, Rc, Box und Interior-Mutability-Wrapper werden vermieden.
Kurze Lifetimes und explizite Ownership-Grenzen sind zu bevorzugen; man kämpft nicht mit künstlichen Leaks oder durchgängig geteiltem Besitz gegen den Borrow Checker.
Standardmäßig unveränderlich
let wird verwendet, solange keine Mutation nötig ist. let mut bleibt lokal und eng gefasst.
Interior Mutability ist kein Standardersatz für gewöhnliches Ownership-Design. Unnötige veränderliche Globals werden nicht angelegt.
Traits erfordern eine echte Abstraktion
Ein Trait entsteht für echtes gemeinsames Verhalten, eine API-Grenze, austauschbare Implementierungen oder eine Test-Naht. Traits werden nicht eingeführt, um Java-Interfaces nachzubilden oder Factory-/Manager-Schichten zu bauen.
Für einzelne konkrete Implementierungen sind einfache inhärente impl-Methoden zu bevorzugen. Generics oder Trait Objects werden nach tatsächlichem Polymorphie-Bedarf gewählt.
Kohäsion vor Größe für `struct`, `impl` und Module
PL-STRUCT-003 gilt für Rust als **Kohäsions-Review**, nicht als festes Zeilenlimit. Ein kohäsives impl oder eine State Machine darf lang sein.
Geteilt wird nach unabhängiger Verantwortung, nicht durch mechanisches Herauslösen von Traits, Structs oder Dateien. Riesige, unzusammenhängende mod.rs- oder lib.rs-Sammelstellen werden vermieden.
Modul- und Dateidichte
Ein Rust-Feature-/Source-Ordner enthält standardmäßig höchstens **10 handgepflegte produktive .rs-Dateien**. Tests, generierter Code, Bindings und reine Metadaten-Dateien zählen nicht mit.
mod.rs, lib.rs und main.rs zählen mit, wenn sie substanzielle gepflegte Logik enthalten, nicht wenn sie nur Modulverzeichnisse oder Re-Exports sind. Das ist eine Review-Schwelle: geteilt wird entlang tatsächlicher Verantwortungsgrenzen, nie in sinnlos verschachtelte Ordner.
Explizite Module mit schmalen öffentlichen Oberflächen sind zu bevorzugen; interne Implementierung bleibt privat.
Rustfmt als Formatierungsautorität
rustfmt / cargo fmt bestimmt Einrückung, Zeilenumbruch, Trailing Commas und idiomatische Formatierung. Kompakte Ausdrücke und Aufrufe sind zu bevorzugen, **wenn rustfmt sie unterstützt**; Pureline fordert keine manuelle Formatierung, die rustfmt wieder rückgängig macht.
Klare Ausdrucksstruktur statt Formatierungsdirektiven, die gegen den Formatter kämpfen. Gewöhnlicher klammerbasierter Kontrollfluss; Javas Semikolon-Regeln werden nicht in ausdrucksorientiertes Rust übertragen.
Diese Regel überschreibt sprachunabhängige Horizontal-Call-Präferenzen, wenn sie mit dem kanonischen Rust-Formatter kollidieren.
Klare Iteration statt cleverer Ketten
Iteratoren, Closures und Combinators sind willkommen, wenn sie eine Transformation direkt ausdrücken. Eine reguläre Schleife ist zu bevorzugen, wenn sie Verzweigung, Seiteneffekte, Borrowing, Debugging oder Hot-Path-Verhalten klarer macht.
Adapter werden nicht verkettet, nur um Zeilen zu sparen.
Async-Tasks haben Ownership und Shutdown
async/.await wird für asynchrone Arbeitslasten verwendet, wo passend. Ein Async-Executor wird nicht mit langlaufender synchroner I/O oder CPU-Arbeit blockiert; dafür dienen dedizierte Blocking-Ausführung oder Worker-Threads.
Gestartete Tasks, Abbruch, Fehlerweitergabe und geordneter Shutdown werden verfolgt. Wichtige Arbeit läuft nicht in versteckten Fire-and-forget-Tasks.
Channels und Synchronisationsprimitive haben klar definierte Ownership und Backpressure. Ein blockierender oder asynchroner Lock wird nie über .await gehalten, außer das Design verlangt es ausdrücklich und unterstützt es sicher.
tokio::spawn(refresh_cache()); Ressourcenlebenszyklen sollen vorhersehbar sein
RAII und Drop sind für synchrones Aufräumen zu bevorzugen. Asynchrone Ressourcen werden explizit awaited oder koordiniert heruntergefahren, weil Drop selbst nicht awaiten kann.
Eine close()- / shutdown()-Methode wird angeboten, wenn die Operation scheitern kann oder vor dem Prozessende abgeschlossen sein muss. Bedeutendes fehlbares Aufräumen wird nicht allein in Destruktoren versteckt.
Unsafe-Code isolieren und begründen
Safe Rust ist der Standard. unsafe kommt nur dort zum Einsatz, wo eine echte FFI-, Hardware- oder Performance-Grenze es verlangt.
Unsafe-Bereiche bleiben klein, ihre Invarianten werden mit // SAFETY: dokumentiert, sie werden hinter einer minimalen sicheren API gekapselt, und die Grenze wird getestet. unsafe wird nicht verwendet, nur um normale Ownership-Prüfungen zu umgehen.
FFI- und Plattformgrenzen sind explizit
Bei Wayland, GPU, Systembibliotheken und ähnlicher nativer Integration bleiben rohe Handles, Lifetime-Garantien, Ownership-Übergabe, Threading-Annahmen und Fehlerkonvertierung sichtbar.
Plattformspezifischer Code wird in fokussierten Modulen isoliert. Generierte Bindings dürfen von gewöhnlicher Formatierung und Dichtelimits ausgenommen sein.
Typen bevorzugen, die Invarianten kodieren
Enums und kleine Newtypes werden verwendet, wo sie echte ungültige Zustände verhindern oder externe Grenzen klären. Nicht jedes Primitiv bekommt ohne Nutzen einen Wrapper.
Konstruktoren, die Invarianten validieren und klare Fehler liefern, sind zu bevorzugen. Ungültige oder uninitialisierte Zustände sollen schwer darstellbar sein.
Macro-Cleverness vermeiden
Funktionen, Traits, Enums und geradlinige Generics sind Macros für Routineverhalten vorzuziehen. Deklarative oder prozedurale Macros kommen zum Einsatz, wenn sie bedeutende strukturelle Wiederholung beseitigen.
Generiertes Verhalten bleibt auffindbar, Diagnosen bleiben verständlich.
Performance messen, bevor Komplexität entsteht
In gemessenen Hot Paths werden unnötige Allokationen, Clones, Reference Counting und Locking vermieden. Vorhersehbare Ownership und zur Arbeitslast passende Datenstrukturen sind zu bevorzugen.
Vor dem Ersetzen sauberen Codes durch komplexe Low-Level-Optimierungen wird gemessen. Bei UI-/Compositor-Workloads wird neben dem Durchsatz auch die Latenz validiert.
Testing und Tooling
cargo fmt --check prüft die Formatierung, cargo clippy läuft mit projektweit vereinbarten Lint-Einstellungen.
Normale, ungültige und Fehlerpfade werden getestet, besonders Abbruch, Lebenszyklus, FFI und Nebenläufigkeitsgrenzen. Warnungen sind bewusst gesetzt; pauschale allow-Attribute, die breite Lint-Gruppen stummschalten, werden vermieden.
Der Pureline Checker muss Rust-Syntax-/AST-bewusst prüfen (einschließlich let ... else, Pattern Matching, Macros und Ausdrucks-Returns), nicht per Textmustern.