@@ -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) ) ; 
@@ -2724,6 +2817,94 @@ impl Path {
27242817        self . file_name ( ) . map ( rsplit_file_at_dot) . and_then ( |( before,  after) | before. and ( after) ) 
27252818    } 
27262819
2820+     /// Checks whether the path ends in a trailing [separator](MAIN_SEPARATOR). 
2821+      /// 
2822+      /// This is generally done to ensure that a path is treated as a directory, not a file, 
2823+      /// although it does not actually guarantee that such a path is a directory on the underlying 
2824+      /// file system. 
2825+      /// 
2826+      /// Despite this behavior, two paths are still considered the same in Rust whether they have a 
2827+      /// trailing separator or not. 
2828+      /// 
2829+      /// # Examples 
2830+      /// 
2831+      /// ``` 
2832+      /// #![feature(path_trailing_sep)] 
2833+      /// use std::path::Path; 
2834+      /// 
2835+      /// assert!(Path::new("dir/").has_trailing_sep()); 
2836+      /// assert!(!Path::new("file.rs").has_trailing_sep()); 
2837+      /// ``` 
2838+      #[ unstable( feature = "path_trailing_sep" ,  issue = "142503" ) ]  
2839+     #[ must_use]  
2840+     #[ inline]  
2841+     pub  fn  has_trailing_sep ( & self )  -> bool  { 
2842+         self . as_os_str ( ) . as_encoded_bytes ( ) . last ( ) . copied ( ) . is_some_and ( is_sep_byte) 
2843+     } 
2844+ 
2845+     /// Ensures that a path has a trailing [separator](MAIN_SEPARATOR), 
2846+      /// allocating a [`PathBuf`] if necessary. 
2847+      /// 
2848+      /// The resulting path will return true for [`has_trailing_sep`](Self::has_trailing_sep). 
2849+      /// 
2850+      /// # Examples 
2851+      /// 
2852+      /// ``` 
2853+      /// #![feature(path_trailing_sep)] 
2854+      /// use std::ffi::OsStr; 
2855+      /// use std::path::Path; 
2856+      /// 
2857+      /// assert_eq!(Path::new("dir//").with_trailing_sep().as_os_str(), OsStr::new("dir//")); 
2858+      /// assert_eq!(Path::new("dir/").with_trailing_sep().as_os_str(), OsStr::new("dir/")); 
2859+      /// assert!(!Path::new("dir").has_trailing_sep()); 
2860+      /// assert!(Path::new("dir").with_trailing_sep().has_trailing_sep()); 
2861+      /// ``` 
2862+      #[ unstable( feature = "path_trailing_sep" ,  issue = "142503" ) ]  
2863+     #[ must_use]  
2864+     #[ inline]  
2865+     pub  fn  with_trailing_sep ( & self )  -> Cow < ' _ ,  Path >  { 
2866+         if  self . has_trailing_sep ( )  {  Cow :: Borrowed ( self )  }  else  {  Cow :: Owned ( self . join ( "" ) )  } 
2867+     } 
2868+ 
2869+     /// Trims a trailing [separator](MAIN_SEPARATOR) from a path, if possible. 
2870+      /// 
2871+      /// The resulting path will return false for [`has_trailing_sep`](Self::has_trailing_sep) for 
2872+      /// most paths. 
2873+      /// 
2874+      /// Some paths, like `/`, cannot be trimmed in this way. 
2875+      /// 
2876+      /// # Examples 
2877+      /// 
2878+      /// ``` 
2879+      /// #![feature(path_trailing_sep)] 
2880+      /// use std::ffi::OsStr; 
2881+      /// use std::path::Path; 
2882+      /// 
2883+      /// assert_eq!(Path::new("dir//").trim_trailing_sep().as_os_str(), OsStr::new("dir")); 
2884+      /// assert_eq!(Path::new("dir/").trim_trailing_sep().as_os_str(), OsStr::new("dir")); 
2885+      /// assert_eq!(Path::new("dir").trim_trailing_sep().as_os_str(), OsStr::new("dir")); 
2886+      /// assert_eq!(Path::new("/").trim_trailing_sep().as_os_str(), OsStr::new("/")); 
2887+      /// assert_eq!(Path::new("//").trim_trailing_sep().as_os_str(), OsStr::new("//")); 
2888+      /// ``` 
2889+      #[ unstable( feature = "path_trailing_sep" ,  issue = "142503" ) ]  
2890+     #[ must_use]  
2891+     #[ inline]  
2892+     pub  fn  trim_trailing_sep ( & self )  -> & Path  { 
2893+         if  self . has_trailing_sep ( )  && ( !self . has_root ( )  || self . parent ( ) . is_some ( ) )  { 
2894+             let  mut  bytes = self . inner . as_encoded_bytes ( ) ; 
2895+             while  let  Some ( ( last,  init) )  = bytes. split_last ( ) 
2896+                 && is_sep_byte ( * last) 
2897+             { 
2898+                 bytes = init; 
2899+             } 
2900+ 
2901+             // SAFETY: Trimming trailing ASCII bytes will retain the validity of the string. 
2902+             Path :: new ( unsafe  {  OsStr :: from_encoded_bytes_unchecked ( bytes)  } ) 
2903+         }  else  { 
2904+             self 
2905+         } 
2906+     } 
2907+ 
27272908    /// Creates an owned [`PathBuf`] with `path` adjoined to `self`. 
27282909     /// 
27292910     /// If `path` is absolute, it replaces the current path. 
@@ -2878,7 +3059,7 @@ impl Path {
28783059     ///   `a/b` all have `a` and `b` as components, but `./a/b` starts with 
28793060     ///   an additional [`CurDir`] component. 
28803061     /// 
2881-      /// * A trailing slash is  normalized away, `/a/b` and `/a/b/` are equivalent. 
3062+      /// * Trailing separators are  normalized away, so  `/a/b` and `/a/b/` are equivalent. 
28823063     /// 
28833064     /// Note that no other normalization takes place; in particular, `a/c` 
28843065     /// and `a/b/../c` are distinct, to account for the possibility that `b` 
@@ -3651,7 +3832,7 @@ impl Error for NormalizeError {}
36513832/// 
36523833/// On POSIX platforms, the path is resolved using [POSIX semantics][posix-semantics], 
36533834/// except that it stops short of resolving symlinks. This means it will keep `..` 
3654- /// components and trailing slashes . 
3835+ /// components and trailing separators . 
36553836/// 
36563837/// On Windows, for verbatim paths, this will simply return the path as given. For other 
36573838/// paths, this is currently equivalent to calling 
0 commit comments