@@ -1412,6 +1412,99 @@ impl PathBuf {
14121412 }
14131413 }
14141414
1415+ /// Sets whether the path has a trailing [separator](MAIN_SEPARATOR).
1416+ ///
1417+ /// The value returned by [`has_trailing_sep`](Path::has_trailing_sep) will be equivalent to
1418+ /// the provided value if possible.
1419+ ///
1420+ /// # Examples
1421+ ///
1422+ /// ```
1423+ /// #![feature(path_trailing_sep)]
1424+ /// use std::path::PathBuf;
1425+ ///
1426+ /// let mut p = PathBuf::from("dir");
1427+ ///
1428+ /// assert!(!p.has_trailing_sep());
1429+ /// p.set_trailing_sep(false);
1430+ /// assert!(!p.has_trailing_sep());
1431+ /// p.set_trailing_sep(true);
1432+ /// assert!(p.has_trailing_sep());
1433+ /// p.set_trailing_sep(false);
1434+ /// assert!(!p.has_trailing_sep());
1435+ ///
1436+ /// p = PathBuf::from("/");
1437+ /// assert!(p.has_trailing_sep());
1438+ /// p.set_trailing_sep(false);
1439+ /// assert!(p.has_trailing_sep());
1440+ /// ```
1441+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
1442+ pub fn set_trailing_sep ( & mut self , trailing_sep : bool ) {
1443+ if trailing_sep { self . push_trailing_sep ( ) } else { self . pop_trailing_sep ( ) }
1444+ }
1445+
1446+ /// Adds a trailing [separator](MAIN_SEPARATOR) to the path.
1447+ ///
1448+ /// This acts similarly to [`Path::with_trailing_sep`], but mutates the underlying `PathBuf`.
1449+ ///
1450+ /// # Examples
1451+ ///
1452+ /// ```
1453+ /// #![feature(path_trailing_sep)]
1454+ /// use std::ffi::OsStr;
1455+ /// use std::path::PathBuf;
1456+ ///
1457+ /// let mut p = PathBuf::from("dir");
1458+ ///
1459+ /// assert!(!p.has_trailing_sep());
1460+ /// p.push_trailing_sep();
1461+ /// assert!(p.has_trailing_sep());
1462+ /// p.push_trailing_sep();
1463+ /// assert!(p.has_trailing_sep());
1464+ ///
1465+ /// p = PathBuf::from("dir/");
1466+ /// p.push_trailing_sep();
1467+ /// assert_eq!(p.as_os_str(), OsStr::new("dir/"));
1468+ /// ```
1469+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
1470+ pub fn push_trailing_sep ( & mut self ) {
1471+ if !self . has_trailing_sep ( ) {
1472+ self . push ( "" ) ;
1473+ }
1474+ }
1475+
1476+ /// Removes a trailing [separator](MAIN_SEPARATOR) from the path, if possible.
1477+ ///
1478+ /// This acts similarly to [`Path::trim_trailing_sep`], but mutates the underlying `PathBuf`.
1479+ ///
1480+ /// # Examples
1481+ ///
1482+ /// ```
1483+ /// #![feature(path_trailing_sep)]
1484+ /// use std::ffi::OsStr;
1485+ /// use std::path::PathBuf;
1486+ ///
1487+ /// let mut p = PathBuf::from("dir//");
1488+ ///
1489+ /// assert!(p.has_trailing_sep());
1490+ /// assert_eq!(p.as_os_str(), OsStr::new("dir//"));
1491+ /// p.pop_trailing_sep();
1492+ /// assert!(!p.has_trailing_sep());
1493+ /// assert_eq!(p.as_os_str(), OsStr::new("dir"));
1494+ /// p.pop_trailing_sep();
1495+ /// assert!(!p.has_trailing_sep());
1496+ /// assert_eq!(p.as_os_str(), OsStr::new("dir"));
1497+ ///
1498+ /// p = PathBuf::from("/");
1499+ /// assert!(p.has_trailing_sep());
1500+ /// p.pop_trailing_sep();
1501+ /// assert!(p.has_trailing_sep());
1502+ /// ```
1503+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
1504+ pub fn pop_trailing_sep ( & mut self ) {
1505+ self . inner . truncate ( self . trim_trailing_sep ( ) . as_os_str ( ) . len ( ) ) ;
1506+ }
1507+
14151508 /// Updates [`self.file_name`] to `file_name`.
14161509 ///
14171510 /// If [`self.file_name`] was [`None`], this is equivalent to pushing
@@ -1612,7 +1705,7 @@ impl PathBuf {
16121705 let new = extension. as_encoded_bytes ( ) ;
16131706 if !new. is_empty ( ) {
16141707 // truncate until right after the file name
1615- // this is necessary for trimming the trailing slash
1708+ // this is necessary for trimming the trailing separator
16161709 let end_file_name = file_name[ file_name. len ( ) ..] . as_ptr ( ) . addr ( ) ;
16171710 let start = self . inner . as_encoded_bytes ( ) . as_ptr ( ) . addr ( ) ;
16181711 self . inner . truncate ( end_file_name. wrapping_sub ( start) ) ;
@@ -2722,6 +2815,94 @@ impl Path {
27222815 self . file_name ( ) . map ( rsplit_file_at_dot) . and_then ( |( before, after) | before. and ( after) )
27232816 }
27242817
2818+ /// Checks whether the path ends in a trailing [separator](MAIN_SEPARATOR).
2819+ ///
2820+ /// This is generally done to ensure that a path is treated as a directory, not a file,
2821+ /// although it does not actually guarantee that such a path is a directory on the underlying
2822+ /// file system.
2823+ ///
2824+ /// Despite this behavior, two paths are still considered the same in Rust whether they have a
2825+ /// trailing separator or not.
2826+ ///
2827+ /// # Examples
2828+ ///
2829+ /// ```
2830+ /// #![feature(path_trailing_sep)]
2831+ /// use std::path::Path;
2832+ ///
2833+ /// assert!(Path::new("dir/").has_trailing_sep());
2834+ /// assert!(!Path::new("file.rs").has_trailing_sep());
2835+ /// ```
2836+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
2837+ #[ must_use]
2838+ #[ inline]
2839+ pub fn has_trailing_sep ( & self ) -> bool {
2840+ self . as_os_str ( ) . as_encoded_bytes ( ) . last ( ) . copied ( ) . is_some_and ( is_sep_byte)
2841+ }
2842+
2843+ /// Ensures that a path has a trailing [separator](MAIN_SEPARATOR),
2844+ /// allocating a [`PathBuf`] if necessary.
2845+ ///
2846+ /// The resulting path will return true for [`has_trailing_sep`](Self::has_trailing_sep).
2847+ ///
2848+ /// # Examples
2849+ ///
2850+ /// ```
2851+ /// #![feature(path_trailing_sep)]
2852+ /// use std::ffi::OsStr;
2853+ /// use std::path::Path;
2854+ ///
2855+ /// assert_eq!(Path::new("dir//").with_trailing_sep().as_os_str(), OsStr::new("dir//"));
2856+ /// assert_eq!(Path::new("dir/").with_trailing_sep().as_os_str(), OsStr::new("dir/"));
2857+ /// assert!(!Path::new("dir").has_trailing_sep());
2858+ /// assert!(Path::new("dir").with_trailing_sep().has_trailing_sep());
2859+ /// ```
2860+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
2861+ #[ must_use]
2862+ #[ inline]
2863+ pub fn with_trailing_sep ( & self ) -> Cow < ' _ , Path > {
2864+ if self . has_trailing_sep ( ) { Cow :: Borrowed ( self ) } else { Cow :: Owned ( self . join ( "" ) ) }
2865+ }
2866+
2867+ /// Trims a trailing [separator](MAIN_SEPARATOR) from a path, if possible.
2868+ ///
2869+ /// The resulting path will return false for [`has_trailing_sep`](Self::has_trailing_sep) for
2870+ /// most paths.
2871+ ///
2872+ /// Some paths, like `/`, cannot be trimmed in this way.
2873+ ///
2874+ /// # Examples
2875+ ///
2876+ /// ```
2877+ /// #![feature(path_trailing_sep)]
2878+ /// use std::ffi::OsStr;
2879+ /// use std::path::Path;
2880+ ///
2881+ /// assert_eq!(Path::new("dir//").trim_trailing_sep().as_os_str(), OsStr::new("dir"));
2882+ /// assert_eq!(Path::new("dir/").trim_trailing_sep().as_os_str(), OsStr::new("dir"));
2883+ /// assert_eq!(Path::new("dir").trim_trailing_sep().as_os_str(), OsStr::new("dir"));
2884+ /// assert_eq!(Path::new("/").trim_trailing_sep().as_os_str(), OsStr::new("/"));
2885+ /// assert_eq!(Path::new("//").trim_trailing_sep().as_os_str(), OsStr::new("//"));
2886+ /// ```
2887+ #[ unstable( feature = "path_trailing_sep" , issue = "142503" ) ]
2888+ #[ must_use]
2889+ #[ inline]
2890+ pub fn trim_trailing_sep ( & self ) -> & Path {
2891+ if self . has_trailing_sep ( ) && ( !self . has_root ( ) || self . parent ( ) . is_some ( ) ) {
2892+ let mut bytes = self . inner . as_encoded_bytes ( ) ;
2893+ while let Some ( ( last, init) ) = bytes. split_last ( )
2894+ && is_sep_byte ( * last)
2895+ {
2896+ bytes = init;
2897+ }
2898+
2899+ // SAFETY: Trimming trailing ASCII bytes will retain the validity of the string.
2900+ Path :: new ( unsafe { OsStr :: from_encoded_bytes_unchecked ( bytes) } )
2901+ } else {
2902+ self
2903+ }
2904+ }
2905+
27252906 /// Creates an owned [`PathBuf`] with `path` adjoined to `self`.
27262907 ///
27272908 /// If `path` is absolute, it replaces the current path.
@@ -2876,7 +3057,7 @@ impl Path {
28763057 /// `a/b` all have `a` and `b` as components, but `./a/b` starts with
28773058 /// an additional [`CurDir`] component.
28783059 ///
2879- /// * A trailing slash is normalized away, `/a/b` and `/a/b/` are equivalent.
3060+ /// * Trailing separators are normalized away, so `/a/b` and `/a/b/` are equivalent.
28803061 ///
28813062 /// Note that no other normalization takes place; in particular, `a/c`
28823063 /// and `a/b/../c` are distinct, to account for the possibility that `b`
@@ -3646,7 +3827,7 @@ impl Error for NormalizeError {}
36463827///
36473828/// On POSIX platforms, the path is resolved using [POSIX semantics][posix-semantics],
36483829/// except that it stops short of resolving symlinks. This means it will keep `..`
3649- /// components and trailing slashes .
3830+ /// components and trailing separators .
36503831///
36513832/// On Windows, for verbatim paths, this will simply return the path as given. For other
36523833/// paths, this is currently equivalent to calling
0 commit comments