LinkAdd#

A new dependency between two tasks, added to a live project through SchedulingChanges::links.add. It has the same fields as LinkData, except that id is optional. Build one with a struct literal. LinkAdd has no constructor and no Default, so every field is set.

Declaration#

#[derive(Clone, Debug)]
pub struct LinkAdd {
    pub id: Option<Id>,
    pub source: Id,
    pub target: Id,
    pub link_type: LinkType,
    pub lag: f64,
    pub lag_unit: Option<TimeUnit>,
}

Fields#

Field Type Default Description
id Option<Id> None Id of the new link. None lets the engine generate one.
source Id required Id of the predecessor task.
target Id required Id of the successor task.
link_type LinkType required Which end of source drives which end of target.
lag f64 required Working time between the two ends, in lag_unit. Must be finite.
lag_unit Option<TimeUnit> None Unit of lag. None uses config.duration_unit.

source, target, link_type, lag and lag_unit mean the same as on LinkData, including what happens when an endpoint names no task.

id#

pub id: Option<Id>

Some(id) stores the link under that id. It must not repeat the id of a link already in the project.

None makes the engine pick the first free id of the form $generated_link_1, $generated_link_2, and so on. The generated id is listed in ChangeEffects::generated_links of the ApplyReport, so you can store it for later updates or removals.

Errors#

A LinkAdd is one operation, named links.add[i]. It's rejected, and listed as a RejectedOperation in ApplyReport::rejected instead of applied, when:

  • id is Some and a link with that id already exists: duplicate link id
  • lag is NaN or infinite: lag must be finite

An endpoint that names no task, or names an unscheduled task, doesn't reject the operation; see ChangeEffects::dropped_links.

A LinkAdd can reuse the id of a link removed in the same change set; see operation order.

Example#

Link task 1 to task 2 in a solved project, and read back the generated link id:

use plen_core::{
    Changes, DateTime, LinkAdd, LinkType, Project, ProjectData, SchedulingChanges,
    SolveOptions, TaskData,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let start = DateTime::from_ymd_hm(2026, 4, 6, 8, 0).ok_or("invalid date")?;
    let data = ProjectData {
        tasks: vec![
            TaskData { start_date: Some(start), duration: Some(2.0), ..TaskData::new(1) },
            TaskData { start_date: Some(start), duration: Some(3.0), ..TaskData::new(2) },
        ],
        ..Default::default()
    };

    let mut project = Project::default();
    let options = SolveOptions::default();
    project.solve(Some(data), &options)?;

    let changes = SchedulingChanges {
        links: Changes {
            add: vec![LinkAdd {
                id: None,
                source: 1.into(),
                target: 2.into(),
                link_type: LinkType::Fs,
                lag: 0.0,
                lag_unit: None,
            }],
            ..Default::default()
        },
        ..Default::default()
    };

    let report = project.apply(&changes)?;
    println!("new link ids: {:?}", report.effects.generated_links);

    let result = project.solve(None, &options)?;
    println!("{:?}", result.updates);
    Ok(())
}

For the full edit cycle, see Editing a project.