@@ -70,6 +70,35 @@ use serde::{Deserialize, Serialize};
7070#[ cfg_attr( feature = "serde" , derive( Serialize , Deserialize ) ) ]
7171pub struct Alphanumeric ;
7272
73+ /// Sample a [`u8`], uniformly distributed over letters:
74+ /// a-z and A-Z.
75+ ///
76+ /// # Example
77+ ///
78+ /// You're able to generate random Alphabetic characters via mapping or via the
79+ /// [`SampleString::sample_string`] method like so:
80+ ///
81+ /// ```
82+ /// use rand::Rng;
83+ /// use rand::distr::{Alphabetic, SampleString};
84+ ///
85+ /// // Manual mapping
86+ /// let mut rng = rand::rng();
87+ /// let chars: String = (0..7).map(|_| rng.sample(Alphabetic) as char).collect();
88+ /// println!("Random chars: {}", chars);
89+ ///
90+ /// // Using [`SampleString::sample_string`]
91+ /// let string = Alphabetic.sample_string(&mut rand::rng(), 16);
92+ /// println!("Random string: {}", string);
93+ /// ```
94+ ///
95+ /// # Passwords
96+ ///
97+ /// Refer to [`Alphanumeric#Passwords`].
98+ #[ derive( Debug , Clone , Copy , Default ) ]
99+ #[ cfg_attr( feature = "serde" , derive( Serialize , Deserialize ) ) ]
100+ pub struct Alphabetic ;
101+
73102// ----- Implementations of distributions -----
74103
75104impl Distribution < char > for StandardUniform {
@@ -123,6 +152,17 @@ impl Distribution<u8> for Alphanumeric {
123152 }
124153}
125154
155+ impl Distribution < u8 > for Alphabetic {
156+ fn sample < R : Rng + ?Sized > ( & self , rng : & mut R ) -> u8 {
157+ const RANGE : u8 = 26 + 26 ;
158+
159+ let offset = rng. random_range ( 0 ..RANGE ) + b'A' ;
160+
161+ // Account for upper-cases
162+ offset + ( offset > b'Z' ) as u8 * ( b'a' - b'Z' - 1 )
163+ }
164+ }
165+
126166#[ cfg( feature = "alloc" ) ]
127167impl SampleString for Alphanumeric {
128168 fn append_string < R : Rng + ?Sized > ( & self , rng : & mut R , string : & mut String , len : usize ) {
@@ -133,6 +173,20 @@ impl SampleString for Alphanumeric {
133173 }
134174}
135175
176+ #[ cfg( feature = "alloc" ) ]
177+ impl SampleString for Alphabetic {
178+ fn append_string < R : Rng + ?Sized > ( & self , rng : & mut R , string : & mut String , len : usize ) {
179+ // SAFETY: With this distribution we guarantee that we're working with valid ASCII
180+ // characters.
181+ // See [#1590](https://github.com/rust-random/rand/issues/1590).
182+ unsafe {
183+ let v = string. as_mut_vec ( ) ;
184+ v. reserve_exact ( len) ;
185+ v. extend ( self . sample_iter ( rng) . take ( len) ) ;
186+ }
187+ }
188+ }
189+
136190impl Distribution < bool > for StandardUniform {
137191 #[ inline]
138192 fn sample < R : Rng + ?Sized > ( & self , rng : & mut R ) -> bool {
@@ -294,6 +348,20 @@ mod tests {
294348 assert ! ( !incorrect) ;
295349 }
296350
351+ #[ test]
352+ fn test_alphabetic ( ) {
353+ let mut rng = crate :: test:: rng ( 806 ) ;
354+
355+ // Test by generating a relatively large number of chars, so we also
356+ // take the rejection sampling path.
357+ let mut incorrect = false ;
358+ for _ in 0 ..100 {
359+ let c: char = rng. sample ( Alphabetic ) . into ( ) ;
360+ incorrect |= !c. is_ascii_alphabetic ( ) ;
361+ }
362+ assert ! ( !incorrect) ;
363+ }
364+
297365 #[ test]
298366 fn value_stability ( ) {
299367 fn test_samples < T : Copy + core:: fmt:: Debug + PartialEq , D : Distribution < T > > (
@@ -321,6 +389,7 @@ mod tests {
321389 ] ,
322390 ) ;
323391 test_samples ( & Alphanumeric , 0 , & [ 104 , 109 , 101 , 51 , 77 ] ) ;
392+ test_samples ( & Alphabetic , 0 , & [ 97 , 102 , 89 , 116 , 75 ] ) ;
324393 test_samples ( & StandardUniform , false , & [ true , true , false , true , false ] ) ;
325394 test_samples (
326395 & StandardUniform ,
0 commit comments