Pureline

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.

Result / Option / ?Ownershiplet ... elserustfmtIsolated unsafe
20Profilregeln

Dieses Profil ist dokumentiert, wird vom Browser-Checker aber noch nicht automatisch geprüft.

Profilregeln

PL-RS-001

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.

Avoid
Java-Prefix an Traits
pub trait IStorage {
}
Pureline
pub(crate) trait Storage {
}
PL-RS-002

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.

Pureline
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 ... else als Guard
let Some(session) = sessions.get(&id) else {
    return Err(SessionError::NotFound);
};

session.start()
PL-RS-003

`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.

Avoid
Result verworfen
let _ = std::fs::write(path, data);
Pureline
fn load_config(path: &Path) -> Result<Config, ConfigError> {
    let content = std::fs::read_to_string(path)?;
    let config = toml::from_str(&content)?;
    Ok(config)
}
PL-RS-004

`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.

Avoid
let config = load_config(path).unwrap();
Pureline
let config = load_config(path)?;
PL-RS-005

`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.

Avoid
Catch-All maskiert Zustände
match state {
    SessionState::Ready => "Ready",
    _ => "Other",
}
Pureline
fn label(state: SessionState) -> &'static str {
    match state {
        SessionState::Ready => "Ready",
        SessionState::Closed => "Closed",
        SessionState::Failed => "Failed",
    }
}
PL-RS-006

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.

PL-RS-007

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.

PL-RS-008

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.

PL-RS-009

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.

PL-RS-010

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.

PL-RS-011

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.

PL-RS-012

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.

PL-RS-013

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.

Avoid
Fire-and-forget
tokio::spawn(refresh_cache());
PL-RS-014

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.

PL-RS-015

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.

PL-RS-016

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.

PL-RS-017

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.

PL-RS-018

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.

PL-RS-019

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.

PL-RS-020

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.