The files family

File rules are the direct view of the extracted Cargo source graph: select source files, choose a mood, and state the condition each selected file must satisfy.

Scope

Start with project_files() or its project_files_in(path) form. Subject selectors can be chained in any order and use AND semantics:

The aliases are files() and files_in(path). See patterns and identifiers for glob and exclusion behavior.

The conditions

Condition Positive mood Negative mood
cycles should().have_no_cycles()
filename should().have_name(pattern) should_not().have_name(pattern)
folder should().be_in_folder(pattern) should_not().be_in_folder(pattern)
path should().be_in_path(pattern) should_not().be_in_path(pattern)
internal dependency should().depend_on_files() should_not().depend_on_files()
external Cargo crate should().depend_on_external_modules() should_not().depend_on_external_modules()
callback should().adhere_to(...) should_not().adhere_to(...)

Each completed condition implements Checkable and returns typed Violation values.

A boundary between two folders

A negative dependency rule is a denylist. Every matching internal edge becomes a FileDependencyViolation carrying the source, target, and raw Rust import evidence.

use archunit::{Checkable, project_files};

let rule = project_files()
    .in_path("src/api/**")
    .should_not()
    .depend_on_files()
    .in_path("src/database/**");

let _: &dyn Checkable = &rule;

A positive dependency rule is an allowlist: every outgoing internal dependency from each selected source must match at least one target selector. Dependencies within the selected source set are not implicitly exempt; list every permitted area deliberately.

External crates

External rules inspect crate names as Rust code sees them. Repeated matching calls are OR alternatives:

use archunit::{Checkable, project_files};

let rule = project_files()
    .in_path("src/common/**")
    .should()
    .depend_on_external_modules()
    .matching("std")
    .matching("core")
    .matching("syn");

let _: &dyn Checkable = &rule;

Use should_not() for a denylist instead. Cargo renames matter: a dependency renamed in Cargo.toml is matched by the name available to source code.

Cycles

have_no_cycles checks cycles wholly inside the selected file set. Rust module ownership can add structural Mod and PubUse edges that are not executable coupling. Exclude those syntax kinds when that distinction is part of the policy:

use archunit::{Checkable, ImportKind, project_files};

let rule = project_files()
    .in_path("src/files**")
    .should()
    .have_no_cycles()
    .excluding_dependency_kinds([ImportKind::Mod, ImportKind::PubUse]);

let _: &dyn Checkable = &rule;

Filtering is evidence-aware. If a merged source-target edge also contains Use, PathReference, or another retained kind, the edge remains in the cycle graph with that evidence.

Naming and placement

Naming and placement conditions use the same matcher targets as the scope:

use archunit::{Checkable, project_files};

let services = project_files()
    .in_folder("src/services")
    .should()
    .have_name("*_service.rs");
let tests = project_files()
    .with_name("*_test.rs")
    .should_not()
    .be_in_path("src/**");

let _: [&dyn Checkable; 2] = [&services, &tests];

A project-specific predicate

adhere_to receives immutable FileInfo for every selected source. It exposes the normalized path, filename without extension, extension, containing directory, full source, and non-blank line count.

use archunit::{Checkable, FileInfo, project_files};

let rule = project_files().in_path("src/**").should().adhere_to(
    |file: &FileInfo| file.non_blank_line_count <= 200,
    "contain at most 200 non-blank lines",
);

let _: &dyn Checkable = &rule;

The stored callback must be Send + Sync + 'static. Panics from user callbacks propagate normally; returning false produces a CustomFileViolation with the supplied message and file facts.

Next, give several file scopes names in the layers family.