Skip to main content

raft_io/
log.rs

1//! The log-storage seam and its in-memory implementation.
2//!
3//! [`RaftLog`] is the boundary between the protocol and where the log actually
4//! lives. The node reads through it (last index, term at an index, an entry)
5//! and writes through it (append, truncate, hard state), and treats a returned
6//! `Ok` from [`sync`](RaftLog::sync) as the durability point: everything
7//! written before a successful `sync` will survive a crash. That contract is
8//! what lets the same protocol run over a throwaway [`MemoryLog`] in tests and a
9//! `wal-db`-backed store in production (arriving in `v0.4`) without the core
10//! knowing the difference.
11//!
12//! Implementors map their own failures into [`Error::Storage`] via
13//! [`Error::storage`](crate::Error::storage), so the trait's error type stays
14//! the crate's own — no associated error type for callers to name.
15
16use crate::error::{Error, Result};
17use crate::types::{HardState, Index, LogEntry, NodeId, Snapshot, Term};
18
19/// Storage for a node's persistent state: its log entries and its
20/// [`HardState`].
21///
22/// Indices are 1-based and contiguous. Index `0` is the sentinel "before the
23/// first entry": [`term_at`](RaftLog::term_at) returns `Some(0)` for it so the
24/// `prev_log_index` consistency check at the head of the log needs no special
25/// case.
26///
27/// # Durability contract
28///
29/// A backend may buffer writes, but once [`sync`](RaftLog::sync) returns `Ok`,
30/// every preceding [`append`](RaftLog::append),
31/// [`truncate`](RaftLog::truncate), and
32/// [`set_hard_state`](RaftLog::set_hard_state) must be durable. The node always
33/// calls `sync` before emitting any message that depends on that state, which
34/// is how it honours Raft's "persist before you respond" rule.
35///
36/// # Examples
37///
38/// Implementing a custom backend means forwarding to your store and mapping its
39/// errors. The in-memory [`MemoryLog`] is the reference implementation; see its
40/// source for the full shape. A read-through usage example:
41///
42/// ```
43/// use raft_io::{LogEntry, MemoryLog, RaftLog};
44///
45/// let mut log = MemoryLog::new();
46/// log.append(&[LogEntry::new(1, 1, b"a".to_vec())]).unwrap();
47/// log.sync().unwrap();
48///
49/// assert_eq!(log.last_index(), 1);
50/// assert_eq!(log.last_term(), 1);
51/// assert_eq!(log.term_at(1), Some(1));
52/// assert_eq!(log.term_at(0), Some(0)); // sentinel
53/// assert_eq!(log.entry(1).unwrap().command, b"a");
54/// ```
55pub trait RaftLog {
56    /// Returns the index of the last entry, or `0` if the log is empty.
57    fn last_index(&self) -> Index;
58
59    /// Returns the term of the last entry, or `0` if the log is empty.
60    fn last_term(&self) -> Term;
61
62    /// Returns the term of the entry at `index`.
63    ///
64    /// Returns `Some(0)` for the sentinel index `0`, `Some(term)` for an entry
65    /// that exists, and `None` for an index past the end of the log.
66    fn term_at(&self, index: Index) -> Option<Term>;
67
68    /// Returns the entry at `index`, or `None` if there is none.
69    fn entry(&self, index: Index) -> Option<LogEntry>;
70
71    /// Returns the entries in the inclusive index range `[from, to]`.
72    ///
73    /// Indices outside the log are skipped, and an empty range (`to < from`, or
74    /// `from == 0`) yields an empty vector. The leader uses this to assemble a
75    /// replication batch. The default implementation reads each index through
76    /// [`entry`](RaftLog::entry); a backend that stores entries contiguously
77    /// should override it with a single bulk read.
78    fn entries(&self, from: Index, to: Index) -> Vec<LogEntry> {
79        if from == 0 || to < from {
80            return Vec::new();
81        }
82        let mut out = Vec::with_capacity((to - from + 1) as usize);
83        let mut index = from;
84        while index <= to {
85            if let Some(entry) = self.entry(index) {
86                out.push(entry);
87            }
88            index += 1;
89        }
90        out
91    }
92
93    /// Appends `entries` to the end of the log.
94    ///
95    /// The first entry's index must be exactly `last_index() + 1` and the
96    /// entries must be contiguous; an implementation must reject a gap or
97    /// overlap rather than corrupt the log. Appending an empty slice is a no-op.
98    ///
99    /// # Errors
100    ///
101    /// Returns [`Error::Storage`] if the entries are not contiguous with the log
102    /// or the backend fails to store them.
103    fn append(&mut self, entries: &[LogEntry]) -> Result<()>;
104
105    /// Removes every entry whose index is `>= from`.
106    ///
107    /// Used to resolve a conflict when a follower's log diverges from the
108    /// leader's (the replication path, `v0.3`). `from` must be `>= 1`; the
109    /// sentinel at index `0` cannot be removed.
110    ///
111    /// # Errors
112    ///
113    /// Returns [`Error::Storage`] if `from` is `0` or the backend fails.
114    fn truncate(&mut self, from: Index) -> Result<()>;
115
116    /// Returns the persisted [`HardState`] (current term and vote).
117    fn hard_state(&self) -> HardState;
118
119    /// Persists `state` as the new [`HardState`].
120    ///
121    /// # Errors
122    ///
123    /// Returns [`Error::Storage`] if the backend fails to store it.
124    fn set_hard_state(&mut self, state: HardState) -> Result<()>;
125
126    /// Flushes all preceding writes to durable storage.
127    ///
128    /// After this returns `Ok`, the durability contract holds for everything
129    /// written so far. The in-memory log has nothing to flush and returns `Ok`
130    /// immediately.
131    ///
132    /// # Errors
133    ///
134    /// Returns [`Error::Storage`] if the backend cannot make its writes durable.
135    fn sync(&mut self) -> Result<()>;
136
137    /// Returns the index the log has been compacted up to — the last index a
138    /// snapshot includes — or `0` if there is no snapshot.
139    ///
140    /// Entries at or below this index are no longer individually available;
141    /// [`snapshot`](RaftLog::snapshot) covers them, and
142    /// [`term_at`](RaftLog::term_at) still answers for the boundary index itself.
143    /// Defaults to `0` for backends without snapshot support.
144    fn snapshot_index(&self) -> Index {
145        0
146    }
147
148    /// Returns the current snapshot, if one exists.
149    ///
150    /// A leader reads this to send a far-behind follower an `InstallSnapshot`
151    /// instead of replaying entries it has already compacted away. Defaults to
152    /// `None`.
153    fn snapshot(&self) -> Option<Snapshot> {
154        None
155    }
156
157    /// Installs `snapshot`, replacing the prefix it subsumes.
158    ///
159    /// Entries up to `snapshot.index` are discarded and the snapshot becomes the
160    /// log's new base. A matching tail — an entry at `snapshot.index` whose term
161    /// is `snapshot.term` — is preserved; otherwise the remaining entries are
162    /// cleared because the snapshot supersedes them. A snapshot no newer than the
163    /// current one is a no-op.
164    ///
165    /// The default implementation returns an error, so a backend that does not
166    /// support snapshots fails loudly rather than silently dropping compaction.
167    ///
168    /// # Errors
169    ///
170    /// Returns [`Error::Storage`] if the backend does not support snapshots or
171    /// fails to store it.
172    fn apply_snapshot(&mut self, snapshot: &Snapshot) -> Result<()> {
173        let _ = snapshot;
174        Err(Error::storage(
175            "apply snapshot",
176            "this log does not support snapshots",
177        ))
178    }
179}
180
181/// An in-memory [`RaftLog`] backed by a `Vec`.
182///
183/// This is the default store and the one [`RaftNode::new`](crate::RaftNode::new)
184/// uses. It keeps entries in a vector (entry at index `i` lives at slot `i - 1`)
185/// and the hard state in a field. Nothing is durable across a process restart —
186/// it is for tests, examples, and the single-node path, not production. Its
187/// operations never fail except on a misuse that would corrupt the log
188/// (a non-contiguous append or a `truncate(0)`).
189///
190/// After a snapshot is installed the log is compacted: entries up to the
191/// snapshot's index are dropped and `base_index` / `base_term` become the log's
192/// new starting boundary, so reads below the boundary return `None` while
193/// [`term_at`](RaftLog::term_at) still answers for the boundary index itself.
194///
195/// # Examples
196///
197/// ```
198/// use raft_io::{HardState, LogEntry, MemoryLog, RaftLog};
199///
200/// let mut log = MemoryLog::new();
201/// assert_eq!(log.last_index(), 0);
202///
203/// log.append(&[LogEntry::new(1, 1, b"x".to_vec())]).unwrap();
204/// log.set_hard_state(HardState { term: 1, voted_for: Some(1) }).unwrap();
205/// log.sync().unwrap();
206///
207/// assert_eq!(log.last_index(), 1);
208/// assert_eq!(log.hard_state().voted_for, Some(1));
209/// ```
210#[derive(Clone, Debug, Default)]
211pub struct MemoryLog {
212    /// Entries with index in `(base_index, base_index + entries.len()]`.
213    entries: Vec<LogEntry>,
214    /// Index of the snapshot boundary (last included), or `0` if none.
215    base_index: Index,
216    /// Term at the snapshot boundary, or `0` if none.
217    base_term: Term,
218    /// Snapshot bytes, present once a snapshot has been installed.
219    snapshot: Option<Vec<u8>>,
220    /// Voting membership recorded by the current snapshot.
221    snapshot_config: Vec<NodeId>,
222    hard: HardState,
223}
224
225impl MemoryLog {
226    /// Creates an empty in-memory log.
227    ///
228    /// # Examples
229    ///
230    /// ```
231    /// use raft_io::{MemoryLog, RaftLog};
232    ///
233    /// let log = MemoryLog::new();
234    /// assert_eq!(log.last_index(), 0);
235    /// ```
236    #[must_use]
237    pub fn new() -> Self {
238        Self::default()
239    }
240
241    /// Returns the number of entries currently stored.
242    ///
243    /// # Examples
244    ///
245    /// ```
246    /// use raft_io::{LogEntry, MemoryLog, RaftLog};
247    ///
248    /// let mut log = MemoryLog::new();
249    /// log.append(&[LogEntry::new(1, 1, vec![])]).unwrap();
250    /// assert_eq!(log.len(), 1);
251    /// ```
252    #[inline]
253    #[must_use]
254    pub fn len(&self) -> usize {
255        self.entries.len()
256    }
257
258    /// Returns `true` if the log holds no entries.
259    ///
260    /// # Examples
261    ///
262    /// ```
263    /// use raft_io::MemoryLog;
264    ///
265    /// assert!(MemoryLog::new().is_empty());
266    /// ```
267    #[inline]
268    #[must_use]
269    pub fn is_empty(&self) -> bool {
270        self.entries.is_empty()
271    }
272}
273
274impl MemoryLog {
275    /// Slot in `entries` for `index`, if it is in range `(base_index, last]`.
276    #[inline]
277    fn slot(&self, index: Index) -> Option<usize> {
278        if index <= self.base_index || index > self.last_index() {
279            None
280        } else {
281            Some((index - self.base_index - 1) as usize)
282        }
283    }
284}
285
286impl RaftLog for MemoryLog {
287    #[inline]
288    fn last_index(&self) -> Index {
289        self.base_index + self.entries.len() as Index
290    }
291
292    #[inline]
293    fn last_term(&self) -> Term {
294        self.entries.last().map_or(self.base_term, |e| e.term)
295    }
296
297    fn term_at(&self, index: Index) -> Option<Term> {
298        if index == self.base_index {
299            return Some(self.base_term);
300        }
301        self.slot(index).map(|s| self.entries[s].term)
302    }
303
304    fn entry(&self, index: Index) -> Option<LogEntry> {
305        self.slot(index).map(|s| self.entries[s].clone())
306    }
307
308    fn entries(&self, from: Index, to: Index) -> Vec<LogEntry> {
309        if from == 0 {
310            return Vec::new();
311        }
312        let from = from.max(self.base_index + 1);
313        if to < from {
314            return Vec::new();
315        }
316        let start = (from - self.base_index - 1) as usize;
317        let end = ((to - self.base_index) as usize).min(self.entries.len());
318        if start >= end {
319            return Vec::new();
320        }
321        self.entries[start..end].to_vec()
322    }
323
324    fn append(&mut self, entries: &[LogEntry]) -> Result<()> {
325        if entries.is_empty() {
326            return Ok(());
327        }
328        let expected = self.last_index() + 1;
329        if entries[0].index != expected {
330            return Err(Error::storage(
331                "append entries",
332                format!(
333                    "non-contiguous append: expected index {expected}, got {}",
334                    entries[0].index
335                ),
336            ));
337        }
338        // The slice itself must be internally contiguous too.
339        for pair in entries.windows(2) {
340            if pair[1].index != pair[0].index + 1 {
341                return Err(Error::storage(
342                    "append entries",
343                    "entries within the batch are not contiguous",
344                ));
345            }
346        }
347        self.entries.extend_from_slice(entries);
348        Ok(())
349    }
350
351    fn truncate(&mut self, from: Index) -> Result<()> {
352        if from <= self.base_index {
353            return Err(Error::storage(
354                "truncate log",
355                "cannot truncate into the snapshot",
356            ));
357        }
358        let keep = (from - self.base_index - 1) as usize;
359        if keep < self.entries.len() {
360            self.entries.truncate(keep);
361        }
362        Ok(())
363    }
364
365    #[inline]
366    fn hard_state(&self) -> HardState {
367        self.hard
368    }
369
370    #[inline]
371    fn set_hard_state(&mut self, state: HardState) -> Result<()> {
372        self.hard = state;
373        Ok(())
374    }
375
376    #[inline]
377    fn sync(&mut self) -> Result<()> {
378        Ok(())
379    }
380
381    #[inline]
382    fn snapshot_index(&self) -> Index {
383        self.base_index
384    }
385
386    fn snapshot(&self) -> Option<Snapshot> {
387        self.snapshot.as_ref().map(|data| {
388            Snapshot::with_config(
389                self.base_index,
390                self.base_term,
391                self.snapshot_config.clone(),
392                data.clone(),
393            )
394        })
395    }
396
397    fn apply_snapshot(&mut self, snapshot: &Snapshot) -> Result<()> {
398        // A snapshot no newer than the one we hold tells us nothing.
399        if snapshot.index <= self.base_index {
400            return Ok(());
401        }
402        // Keep the tail only if our log agrees with the snapshot at its boundary.
403        if self.term_at(snapshot.index) == Some(snapshot.term) {
404            let drop = ((snapshot.index - self.base_index) as usize).min(self.entries.len());
405            let _ = self.entries.drain(0..drop);
406        } else {
407            self.entries.clear();
408        }
409        self.base_index = snapshot.index;
410        self.base_term = snapshot.term;
411        self.snapshot = Some(snapshot.data.clone());
412        self.snapshot_config = snapshot.config.clone();
413        Ok(())
414    }
415}
416
417#[cfg(test)]
418mod tests {
419    #![allow(clippy::unwrap_used, clippy::expect_used)]
420
421    use super::*;
422
423    fn entry(term: Term, index: Index) -> LogEntry {
424        LogEntry::new(term, index, vec![index as u8])
425    }
426
427    #[test]
428    fn test_empty_log_reports_zero() {
429        let log = MemoryLog::new();
430        assert_eq!(log.last_index(), 0);
431        assert_eq!(log.last_term(), 0);
432        assert!(log.is_empty());
433        assert_eq!(log.entry(0), None);
434        assert_eq!(log.entry(1), None);
435    }
436
437    #[test]
438    fn test_term_at_sentinel_is_zero() {
439        assert_eq!(MemoryLog::new().term_at(0), Some(0));
440    }
441
442    #[test]
443    fn test_append_and_read_back() {
444        let mut log = MemoryLog::new();
445        log.append(&[entry(1, 1), entry(1, 2)]).unwrap();
446        log.append(&[entry(2, 3)]).unwrap();
447        assert_eq!(log.last_index(), 3);
448        assert_eq!(log.last_term(), 2);
449        assert_eq!(log.term_at(2), Some(1));
450        assert_eq!(log.term_at(3), Some(2));
451        assert_eq!(log.term_at(4), None);
452        assert_eq!(log.entry(3).unwrap().term, 2);
453    }
454
455    #[test]
456    fn test_append_empty_is_noop() {
457        let mut log = MemoryLog::new();
458        log.append(&[]).unwrap();
459        assert_eq!(log.last_index(), 0);
460    }
461
462    #[test]
463    fn test_append_rejects_gap() {
464        let mut log = MemoryLog::new();
465        let err = log.append(&[entry(1, 2)]).unwrap_err();
466        assert!(matches!(err, Error::Storage { .. }));
467    }
468
469    #[test]
470    fn test_append_rejects_internally_noncontiguous_batch() {
471        let mut log = MemoryLog::new();
472        let err = log.append(&[entry(1, 1), entry(1, 3)]).unwrap_err();
473        assert!(matches!(err, Error::Storage { .. }));
474    }
475
476    #[test]
477    fn test_entries_range_inclusive() {
478        let mut log = MemoryLog::new();
479        log.append(&[entry(1, 1), entry(1, 2), entry(2, 3), entry(2, 4)])
480            .unwrap();
481        let mid = log.entries(2, 3);
482        assert_eq!(mid.len(), 2);
483        assert_eq!(mid[0].index, 2);
484        assert_eq!(mid[1].index, 3);
485    }
486
487    #[test]
488    fn test_entries_range_clamps_and_handles_empty() {
489        let mut log = MemoryLog::new();
490        log.append(&[entry(1, 1), entry(1, 2)]).unwrap();
491        // Past the end is clamped.
492        assert_eq!(log.entries(1, 99).len(), 2);
493        // Empty / degenerate ranges yield nothing.
494        assert!(log.entries(3, 2).is_empty());
495        assert!(log.entries(0, 5).is_empty());
496        assert!(log.entries(5, 9).is_empty());
497    }
498
499    #[test]
500    fn test_default_entries_matches_override() {
501        // Drive the trait's default `entries` impl through a wrapper that does
502        // not override it, and confirm it agrees with `MemoryLog`'s bulk read.
503        struct Wrap(MemoryLog);
504        impl RaftLog for Wrap {
505            fn last_index(&self) -> Index {
506                self.0.last_index()
507            }
508            fn last_term(&self) -> Term {
509                self.0.last_term()
510            }
511            fn term_at(&self, index: Index) -> Option<Term> {
512                self.0.term_at(index)
513            }
514            fn entry(&self, index: Index) -> Option<LogEntry> {
515                self.0.entry(index)
516            }
517            fn append(&mut self, entries: &[LogEntry]) -> Result<()> {
518                self.0.append(entries)
519            }
520            fn truncate(&mut self, from: Index) -> Result<()> {
521                self.0.truncate(from)
522            }
523            fn hard_state(&self) -> HardState {
524                self.0.hard_state()
525            }
526            fn set_hard_state(&mut self, state: HardState) -> Result<()> {
527                self.0.set_hard_state(state)
528            }
529            fn sync(&mut self) -> Result<()> {
530                self.0.sync()
531            }
532        }
533        let mut inner = MemoryLog::new();
534        inner
535            .append(&[entry(1, 1), entry(1, 2), entry(2, 3)])
536            .unwrap();
537        let wrap = Wrap(inner.clone());
538        assert_eq!(wrap.entries(1, 3), inner.entries(1, 3));
539        assert_eq!(wrap.entries(2, 2), inner.entries(2, 2));
540    }
541
542    #[test]
543    fn test_truncate_removes_tail() {
544        let mut log = MemoryLog::new();
545        log.append(&[entry(1, 1), entry(1, 2), entry(1, 3)])
546            .unwrap();
547        log.truncate(2).unwrap();
548        assert_eq!(log.last_index(), 1);
549        assert_eq!(log.entry(2), None);
550    }
551
552    #[test]
553    fn test_truncate_past_end_is_noop() {
554        let mut log = MemoryLog::new();
555        log.append(&[entry(1, 1)]).unwrap();
556        log.truncate(5).unwrap();
557        assert_eq!(log.last_index(), 1);
558    }
559
560    #[test]
561    fn test_truncate_zero_is_rejected() {
562        let mut log = MemoryLog::new();
563        assert!(log.truncate(0).is_err());
564    }
565
566    #[test]
567    fn test_hard_state_round_trips() {
568        let mut log = MemoryLog::new();
569        let hs = HardState {
570            term: 4,
571            voted_for: Some(2),
572        };
573        log.set_hard_state(hs).unwrap();
574        assert_eq!(log.hard_state(), hs);
575    }
576
577    #[test]
578    fn test_sync_is_ok() {
579        assert!(MemoryLog::new().sync().is_ok());
580    }
581
582    // ---- compaction / snapshots -------------------------------------------
583
584    #[test]
585    fn test_apply_snapshot_compacts_and_keeps_matching_tail() {
586        let mut log = MemoryLog::new();
587        log.append(&[entry(1, 1), entry(1, 2), entry(2, 3), entry(2, 4)])
588            .unwrap();
589        // Snapshot through index 2 (term 1) — our log matches there, keep the tail.
590        log.apply_snapshot(&Snapshot::new(2, 1, b"state@2".to_vec()))
591            .unwrap();
592
593        assert_eq!(log.snapshot_index(), 2);
594        assert_eq!(log.last_index(), 4);
595        // Compacted entries are gone, the boundary term is still answerable.
596        assert_eq!(log.entry(1), None);
597        assert_eq!(log.entry(2), None);
598        assert_eq!(log.term_at(2), Some(1)); // boundary
599        assert_eq!(log.term_at(1), None); // below boundary
600        // The tail survived.
601        assert_eq!(log.entry(3).unwrap().term, 2);
602        assert_eq!(log.entry(4).unwrap().index, 4);
603        // The snapshot is retrievable.
604        assert_eq!(log.snapshot().unwrap().data, b"state@2");
605    }
606
607    #[test]
608    fn test_apply_snapshot_clears_log_on_mismatch() {
609        let mut log = MemoryLog::new();
610        log.append(&[entry(1, 1), entry(1, 2)]).unwrap();
611        // A snapshot at index 5 our log cannot match supersedes everything.
612        log.apply_snapshot(&Snapshot::new(5, 3, b"state@5".to_vec()))
613            .unwrap();
614        assert_eq!(log.snapshot_index(), 5);
615        assert_eq!(log.last_index(), 5);
616        assert_eq!(log.last_term(), 3);
617        assert!(log.entries(1, 5).is_empty());
618        assert_eq!(log.term_at(5), Some(3));
619    }
620
621    #[test]
622    fn test_append_continues_after_snapshot() {
623        let mut log = MemoryLog::new();
624        log.apply_snapshot(&Snapshot::new(7, 2, b"base".to_vec()))
625            .unwrap();
626        assert_eq!(log.last_index(), 7);
627        // Next append must be contiguous with the snapshot boundary.
628        assert!(log.append(&[entry(2, 7)]).is_err()); // 7 already covered
629        log.append(&[entry(3, 8), entry(3, 9)]).unwrap();
630        assert_eq!(log.last_index(), 9);
631        assert_eq!(log.entry(8).unwrap().term, 3);
632        assert_eq!(log.term_at(7), Some(2)); // boundary term preserved
633    }
634
635    #[test]
636    fn test_stale_snapshot_is_ignored() {
637        let mut log = MemoryLog::new();
638        log.apply_snapshot(&Snapshot::new(5, 2, b"new".to_vec()))
639            .unwrap();
640        log.apply_snapshot(&Snapshot::new(3, 1, b"old".to_vec()))
641            .unwrap();
642        assert_eq!(log.snapshot_index(), 5);
643        assert_eq!(log.snapshot().unwrap().data, b"new");
644    }
645
646    #[test]
647    fn test_truncate_into_snapshot_is_rejected() {
648        let mut log = MemoryLog::new();
649        log.apply_snapshot(&Snapshot::new(5, 2, b"s".to_vec()))
650            .unwrap();
651        assert!(log.truncate(5).is_err());
652        assert!(log.truncate(3).is_err());
653    }
654
655    #[test]
656    fn test_default_apply_snapshot_errors() {
657        // The trait's default `apply_snapshot` rejects, so a snapshot-unaware
658        // backend fails loudly.
659        struct NoSnap(MemoryLog);
660        impl RaftLog for NoSnap {
661            fn last_index(&self) -> Index {
662                self.0.last_index()
663            }
664            fn last_term(&self) -> Term {
665                self.0.last_term()
666            }
667            fn term_at(&self, index: Index) -> Option<Term> {
668                self.0.term_at(index)
669            }
670            fn entry(&self, index: Index) -> Option<LogEntry> {
671                self.0.entry(index)
672            }
673            fn append(&mut self, entries: &[LogEntry]) -> Result<()> {
674                self.0.append(entries)
675            }
676            fn truncate(&mut self, from: Index) -> Result<()> {
677                self.0.truncate(from)
678            }
679            fn hard_state(&self) -> HardState {
680                self.0.hard_state()
681            }
682            fn set_hard_state(&mut self, state: HardState) -> Result<()> {
683                self.0.set_hard_state(state)
684            }
685            fn sync(&mut self) -> Result<()> {
686                self.0.sync()
687            }
688        }
689        let mut log = NoSnap(MemoryLog::new());
690        assert_eq!(log.snapshot_index(), 0);
691        assert!(log.snapshot().is_none());
692        assert!(log.apply_snapshot(&Snapshot::new(1, 1, vec![])).is_err());
693    }
694}