io_truncate/
lib.rs

1//! IO objects that can be shortened.
2//!
3//! See the [`Truncate`] trait.
4
5use std::{
6    cmp,
7    fs::File,
8    io::{Cursor, Error, ErrorKind},
9};
10
11/// A trait for IO objects that can be shortened.
12///
13/// See the documentation comments on individual implementations for some potentially important
14/// notes on their specific behaviors.
15pub trait Truncate {
16    /// Truncate the object to the given new length in bytes.
17    ///
18    /// The behavior when `new_len` is larger than the current length of the object is unspecified.
19    /// Implementations may choose to panic or extend the data in some way.
20    ///
21    /// # Example
22    ///
23    /// ```
24    /// # use io_truncate::Truncate;
25    /// let mut v: &[u8] = &[0, 1, 2, 3];
26    /// v.truncate(3).unwrap();
27    /// assert_eq!(v, &[0, 1, 2]);
28    /// ```
29    fn truncate(&mut self, new_len: usize) -> Result<(), Error>;
30}
31
32impl Truncate for File {
33    /// Delegates to [`File::set_len`].
34    fn truncate(&mut self, new_len: usize) -> Result<(), Error> {
35        self.set_len(new_len as u64)
36    }
37}
38
39impl Truncate for Vec<u8> {
40    /// Shortens the `Vec` or returns an error if the length would be larger than the current
41    /// length.
42    fn truncate(&mut self, new_len: usize) -> Result<(), Error> {
43        if new_len <= self.len() {
44            self.truncate(new_len);
45            Ok(())
46        } else {
47            Err(Error::new(
48                ErrorKind::InvalidInput,
49                format!(
50                    "tried to truncate to greater length ({} > {})",
51                    new_len,
52                    self.len()
53                ),
54            ))
55        }
56    }
57}
58
59impl<'a> Truncate for &'a [u8] {
60    /// Shortens the slice or returns and error if the length would be larger than the current
61    /// length.
62    fn truncate(&mut self, new_len: usize) -> Result<(), Error> {
63        if new_len <= self.len() {
64            *self = &self[..new_len];
65            Ok(())
66        } else {
67            Err(Error::new(
68                ErrorKind::InvalidInput,
69                format!(
70                    "tried to truncate to greater length ({} > {})",
71                    new_len,
72                    self.len()
73                ),
74            ))
75        }
76    }
77}
78
79impl<T> Truncate for Cursor<T>
80where
81    T: Truncate,
82{
83    /// Delegates to the contained [`Truncate`] impl. The cursor will be moved to the end of the
84    /// data if it lies in the truncated area.
85    fn truncate(&mut self, new_len: usize) -> Result<(), Error> {
86        self.get_mut().truncate(new_len)?;
87        self.set_position(cmp::min(new_len as u64, self.position()));
88        Ok(())
89    }
90}
91
92impl<T> Truncate for &mut T
93where
94    T: Truncate,
95{
96    fn truncate(&mut self, new_len: usize) -> Result<(), Error> {
97        (**self).truncate(new_len)
98    }
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use std::io::{Seek, SeekFrom, Write};
105
106    #[test]
107    fn vec() {
108        let mut v: Vec<u8> = vec![0, 1, 2, 3];
109
110        // Need to call like this in order to not conflict with the inherent method.
111        Truncate::truncate(&mut v, 3).unwrap();
112        assert_eq!(v, &[0, 1, 2]);
113
114        // Error
115        let e = Truncate::truncate(&mut v, 4).unwrap_err();
116        assert_eq!(e.kind(), ErrorKind::InvalidInput);
117    }
118
119    #[test]
120    fn slice() {
121        let mut v: &[u8] = &[0, 1, 2, 3];
122
123        v.truncate(3).unwrap();
124        assert_eq!(v, &[0, 1, 2]);
125
126        // Error
127        let e = v.truncate(4).unwrap_err();
128        assert_eq!(e.kind(), ErrorKind::InvalidInput);
129    }
130
131    #[test]
132    fn cursor() {
133        let mut v: Cursor<&[u8]> = Cursor::new(&[0, 1, 2, 3]);
134
135        v.set_position(4); // end of data
136        v.truncate(3).unwrap();
137        assert_eq!(v.get_ref(), &[0, 1, 2]);
138        assert_eq!(v.position(), 3);
139
140        // Error
141        let e = v.truncate(4).unwrap_err();
142        assert_eq!(e.kind(), ErrorKind::InvalidInput);
143    }
144
145    #[test]
146    fn file() {
147        let mut f = tempfile::tempfile().unwrap();
148        f.write_all(&[0, 1, 2, 3]).unwrap();
149        f.seek(SeekFrom::Start(0)).unwrap();
150
151        f.truncate(3).unwrap();
152        assert_eq!(f.seek(SeekFrom::End(0)).unwrap(), 3);
153
154        // File::set_len works with longer values too
155    }
156}