qubit_io/wrappers/position_guard.rs
1/*******************************************************************************
2 *
3 * Copyright (c) 2026 Haixing Hu.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * Licensed under the Apache License, Version 2.0.
8 *
9 ******************************************************************************/
10use std::io::{
11 Result,
12 Seek,
13 SeekFrom,
14};
15
16/// Guard that restores a seekable stream to its original position.
17///
18/// `PositionGuard` captures the stream position when it is created and seeks
19/// back to that position when the guard is dropped, unless
20/// [`PositionGuard::restore`] or [`PositionGuard::dismiss`] has already
21/// completed. Drop-time restoration errors are ignored because [`Drop::drop`]
22/// cannot return a [`Result`]; call [`PositionGuard::restore`] when the error
23/// must be observed.
24///
25/// # Examples
26/// ```
27/// use std::io::{
28/// Cursor,
29/// Seek,
30/// SeekFrom,
31/// };
32///
33/// use qubit_io::PositionGuard;
34///
35/// let mut stream = Cursor::new(b"abcdef".to_vec());
36/// stream.seek(SeekFrom::Start(2))?;
37///
38/// {
39/// let mut guard = PositionGuard::new(&mut stream)?;
40/// guard.get_mut().seek(SeekFrom::End(0))?;
41/// }
42///
43/// assert_eq!(2, stream.position());
44/// # Ok::<(), std::io::Error>(())
45/// ```
46pub struct PositionGuard<'a, S>
47where
48 S: Seek + ?Sized,
49{
50 stream: &'a mut S,
51 position: u64,
52 done: bool,
53}
54
55impl<'a, S> PositionGuard<'a, S>
56where
57 S: Seek + ?Sized,
58{
59 /// Captures the current position of `stream`.
60 ///
61 /// # Parameters
62 /// - `stream`: Seekable stream to guard.
63 ///
64 /// # Returns
65 /// A guard that will restore the captured position on drop.
66 ///
67 /// # Errors
68 /// Returns the error reported by [`Seek::stream_position`] when the current
69 /// position cannot be read.
70 #[inline]
71 pub fn new(stream: &'a mut S) -> Result<Self> {
72 let position = stream.stream_position()?;
73 Ok(Self {
74 stream,
75 position,
76 done: false,
77 })
78 }
79
80 /// Returns the captured stream position.
81 ///
82 /// # Returns
83 /// The position captured when this guard was created.
84 #[inline]
85 pub fn position(&self) -> u64 {
86 self.position
87 }
88
89 /// Returns a mutable reference to the guarded stream.
90 ///
91 /// # Returns
92 /// The guarded stream reference.
93 #[inline]
94 pub fn get_mut(&mut self) -> &mut S {
95 self.stream
96 }
97
98 /// Restores the captured position immediately.
99 ///
100 /// After a successful restore, drop-time restoration is disabled. If
101 /// restoring fails, drop will still make a best-effort restore attempt.
102 ///
103 /// # Errors
104 /// Returns the error reported by [`Seek::seek`] when the stream cannot seek
105 /// back to the captured position.
106 pub fn restore(&mut self) -> Result<()> {
107 self.stream.seek(SeekFrom::Start(self.position)).map(|_| {
108 self.done = true;
109 })
110 }
111
112 /// Disables drop-time restoration without moving the stream.
113 ///
114 /// This is useful when the caller intentionally wants to keep the stream at
115 /// its current position.
116 #[inline]
117 pub fn dismiss(mut self) {
118 self.done = true;
119 }
120}
121
122impl<S> Drop for PositionGuard<'_, S>
123where
124 S: Seek + ?Sized,
125{
126 fn drop(&mut self) {
127 if !self.done {
128 drop(self.stream.seek(SeekFrom::Start(self.position)));
129 }
130 }
131}