rust
58 lines · 8 steps
Applying a diff hunk in Rust
A patch applier walks a hunk's lines against a buffer, verifying context before adding or removing text.
Explained by
highlit
1use std::collections::VecDeque;
2
3#[derive(Debug)]
4pub struct Hunk {
5 pub old_start: usize,
6 pub lines: Vec<HunkLine>,
7}
8
9#[derive(Debug)]
10pub enum HunkLine {
11 Context(String),
12 Removed(String),
13 Added(String),
14}
15
16#[derive(Debug)]
17pub enum PatchError {
18 ContextMismatch { line: usize, expected: String, found: String },
19 UnexpectedEof { line: usize },
20}
21
22pub fn apply_hunk(buffer: &[String], hunk: &Hunk) -> Result<Vec<String>, PatchError> {
23 let mut out: Vec<String> = buffer[..hunk.old_start.saturating_sub(1)].to_vec();
24 let mut cursor = hunk.old_start.saturating_sub(1);
25 let mut pending: VecDeque<&HunkLine> = hunk.lines.iter().collect();
26
27 while let Some(line) = pending.pop_front() {
28 match line {
29 HunkLine::Context(text) => {
30 let existing = buffer.get(cursor).ok_or(PatchError::UnexpectedEof { line: cursor + 1 })?;
31 if existing != text {
32 return Err(PatchError::ContextMismatch {
33 line: cursor + 1,
34 expected: text.clone(),
35 found: existing.clone(),
36 });
37 }
38 out.push(existing.clone());
39 cursor += 1;
40 }
41 HunkLine::Removed(text) => {
42 let existing = buffer.get(cursor).ok_or(PatchError::UnexpectedEof { line: cursor + 1 })?;
43 if existing != text {
44 return Err(PatchError::ContextMismatch {
45 line: cursor + 1,
46 expected: text.clone(),
47 found: existing.clone(),
48 });
49 }
50 cursor += 1;
51 }
52 HunkLine::Added(text) => out.push(text.clone()),
53 }
54 }
55
56 out.extend_from_slice(&buffer[cursor..]);
57 Ok(out)
58}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Modeling each diff line as an enum variant lets one match express context, removal, and addition cleanly.
- 2Verifying context and removed lines against the buffer catches stale patches before corrupting output.
- 3Returning a rich error enum lets callers report exactly which line mismatched and why.
Related explainers
rust
use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(untagged)]
Parsing flexible JSON shapes with serde
deserialization
enums
json
Intermediate
6 steps
ruby
require "shellwords" require "open3" module Backup
Building safe shell commands in Ruby
shell-out
subprocess
command-injection
Intermediate
7 steps
rust
use std::cmp::{Ordering, Reverse}; use std::collections::BinaryHeap; use std::fs::File; use std::io::{self, BufRead, BufReader, BufWriter, Lines, Write};
K-way merge of sorted logs in Rust
binary-heap
k-way-merge
streaming-io
Intermediate
8 steps
ruby
class UserAgentParser BROWSERS = [ [/Edg\/([\d.]+)/, "Edge"], [/OPR\/([\d.]+)/, "Opera"],
Parsing user-agent strings in Ruby
regex
pattern-matching
lookup-tables
Intermediate
8 steps
rust
use axum::{ extract::{Path, State}, response::sse::{Event, KeepAlive, Sse}, };
Streaming import progress with SSE in Axum
server-sent-events
streams
watch-channel
Advanced
7 steps
rust
use std::f64::consts::PI; #[derive(Debug, Clone, Copy)] pub struct LatLng {
Building geographic bounding boxes in Rust
geospatial
value-types
option
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/applying-a-diff-hunk-in-rust-explained-rust-8490/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.