Quick start#
By the end of this page, you'll have a Rust program that schedules two linked tasks, and a live project that reschedules them when one changes.
Requirements#
- A Rust toolchain with edition 2021. The crate doesn't declare a minimum Rust version.
- No async runtime. Every call is synchronous and CPU-only, with no I/O and no clock.
Install#
The crate name is plen-core. In code you import it as plen_core.
Schedule a snapshot#
Start with the smallest useful plan: two tasks and a finish-to-start link. Both tasks start on Monday, 2026-08-10, at 08:00, so task 2 starts too early. It must wait for task 1 to finish.
use ;
Run it with cargo run:
task 2: 2026-08-12 08:00 -> 2026-08-13 08:00
schedule takes a ProjectData snapshot and returns a ScheduleResult. Its updates list holds only the tasks that moved, as Mutation records. Task 1 didn't move, so it's not in the list.
Durations are working time in days. The default calendar works Monday to Friday, 08:00-12:00 and 13:00-17:00. That's why the two-day task 1 ends on Wednesday at 08:00, not on Tuesday evening.
Dates are DateTime values: local time, minute precision, no time zone. DateTime::from_ymd_hm returns an Option, so the example turns None into an error instead of calling unwrap(). Ids are Id values, and 1.into() and "1".into() name the same task.
When the input is invalid#
Every leaf task needs two of start_date, end_date and duration. If you leave out task 2's start date and keep only its duration, schedule rejects the snapshot before it schedules anything:
Error: Input(InputError { kind: Validation, message: "task needs two of start_date / end_date / duration", path: Some("tasks[1].start_date") })
The InputError has a path that points to the field that failed. Add the start date back and the program runs again.
Bad input is the only thing that fails this call. Dependency cycles, constraint conflicts and dropped links come back as data in ScheduleResult (cycles, conflicts, notes), and the call still returns Ok.
Keep a live project#
schedule fits batch jobs and request handlers that see each snapshot once. An editor works differently. It loads the plan once, then sends small changes as the user works. That's what Project does.
This program loads the same two tasks, previews a change, then commits it:
use ;
Output:
loaded, 1 task(s) moved
preview: 2 task(s) would move
task 1: 2026-08-10 08:00 -> 2026-08-13 08:00
task 2: 2026-08-13 08:00 -> 2026-08-14 08:00
Each call does one job:
Project::solvewithSome(data)loads and schedules the snapshot. Its dates become the project's baseline.Project::previewruns the change on a copy of the baseline. The project doesn't change, so it's safe to call on every mouse move during a drag.Project::applycommits the change and marks the project as unsolved.solve(None, ...)reschedules the current state. Task 1 is reported because it was edited, and task 2 moves to follow it.
Edits are SchedulingChanges. A TaskUpdate sets only the fields you give it. Each field is a Patch: Patch::Value sets it, Patch::Null clears it, and Patch::Missing (the default) leaves it alone.
When an edit is rejected#
apply accepts what it can and reports the rest in the returned ApplyReport. It doesn't return an error for a bad operation. Remove a task that doesn't exist, and the report gets this entry in rejected:
tasks.remove[0] unknown task id
needs_resync is now true. Your copy of the plan and the project's copy no longer match, so the next solve(None, ...) fails with Error::Project(ProjectError::NeedsResync) (displayed as needs-resync). To fix it, send the full data again with solve(Some(data), ...). That's why the example checks needs_resync before it solves. Recover from rejected changes covers this flow in detail.
Where to go next#
- Project data: tasks, links, calendars, resources and assignments.
- Scheduling a project: one-shot and stateful scheduling, options and subtrees.
- Reading results: updates, conflicts, cycles and notes.
- Working-time calendars: hours, weekdays and date exceptions.
- Data Updates:
apply, baselines and resync. - Previewing changes: what-if results without committing.
- Leveling resources: remove resource overallocation.
- Error handling:
InputError,ProjectErrorand what's data instead of an error. - API reference: every public type and function.