@@ -946,6 +946,46 @@ macro_rules! impl_scalar_lhs_binop {
946946// Every type with an `ArrayElement` impl, so the two directions stay symmetric.
947947impl_scalar_lhs_binop ! ( bool , u8 , u16 , u32 , u64 , i8 , i16 , i32 , i64 , f32 , f64 ) ;
948948
949+ // `a += &b`, for both array and scalar right-hand sides.
950+ //
951+ // These do **not** mutate in place. MLX has no in-place ops: the op builds a new
952+ // array and `a` is rebound to it, dropping the old handle. So `a += &b` is
953+ // exactly `a = &a + &b` with less typing, and it saves no memory. Two visible
954+ // consequences: `a` must be owned and `mut` (not a `&Array`), and the dtype can
955+ // change under it, since MLX promotes — `a += 1.0f64` leaves `a` float64.
956+ //
957+ // Same default-stream and panic-on-failure contract as the operators above.
958+ macro_rules! impl_op_assign {
959+ ( $( $trait: ident :: $method: ident => $op: ident) ,* $( , ) ?) => {
960+ $(
961+ impl std:: ops:: $trait<& Array > for Array {
962+ fn $method( & mut self , rhs: & Array ) {
963+ * self = self . $op( rhs, & Stream :: default ( ) ) . unwrap_or_else( |e| {
964+ panic!( concat!( "Array::" , stringify!( $op) , " failed: {}" ) , e)
965+ } ) ;
966+ }
967+ }
968+
969+ impl <T : ArrayElement > std:: ops:: $trait<T > for Array {
970+ fn $method( & mut self , rhs: T ) {
971+ * self = self
972+ . $op( & Array :: from_scalar( rhs) , & Stream :: default ( ) )
973+ . unwrap_or_else( |e| {
974+ panic!( concat!( "Array::" , stringify!( $op) , " failed: {}" ) , e)
975+ } ) ;
976+ }
977+ }
978+ ) *
979+ } ;
980+ }
981+
982+ impl_op_assign ! {
983+ AddAssign :: add_assign => add,
984+ SubAssign :: sub_assign => subtract,
985+ MulAssign :: mul_assign => multiply,
986+ DivAssign :: div_assign => divide,
987+ }
988+
949989#[ cfg( test) ]
950990mod tests {
951991 use super :: * ;
@@ -1317,12 +1357,6 @@ mod tests {
13171357 assert_eq ! ( ints. add( & floats, & s) . unwrap( ) . dtype( ) , Dtype :: Float32 ) ;
13181358 }
13191359
1320- #[ test]
1321- #[ should_panic( expected = "array dtype float32 does not match" ) ]
1322- fn reading_the_wrong_element_type_panics ( ) {
1323- let _ = Array :: from_slice ( & [ 1.0f32 , 2.0 ] , & [ 2 ] ) . to_vec :: < i32 > ( ) ;
1324- }
1325-
13261360 #[ test]
13271361 fn comparisons_produce_bool_masks ( ) {
13281362 let s = Stream :: cpu ( ) ;
@@ -1728,7 +1762,7 @@ mod tests {
17281762 }
17291763
17301764 #[ test]
1731- #[ should_panic( expected = "does not match requested element type" ) ]
1765+ #[ should_panic( expected = "array dtype int32 does not match requested element type" ) ]
17321766 fn to_vec_wrong_dtype_panics ( ) {
17331767 let ints = Array :: from_slice ( & [ 1i32 , 2 , 3 ] , & [ 3 ] ) ;
17341768 let _ = ints. to_vec :: < f32 > ( ) ;
@@ -1759,6 +1793,61 @@ mod tests {
17591793 assert_eq ! ( a. to_vec:: <f32 >( ) , vec![ 10.0 , 20.0 , 30.0 ] ) ;
17601794 }
17611795
1796+ #[ test]
1797+ fn assigning_operators_match_methods ( ) {
1798+ let b = Array :: from_slice ( & [ 1.0f32 , 2.0 , 3.0 ] , & [ 3 ] ) ;
1799+
1800+ let mut a = Array :: from_slice ( & [ 10.0f32 , 20.0 , 30.0 ] , & [ 3 ] ) ;
1801+ a += & b;
1802+ assert_eq ! ( a. to_vec:: <f32 >( ) , vec![ 11.0 , 22.0 , 33.0 ] ) ;
1803+ a -= & b;
1804+ assert_eq ! ( a. to_vec:: <f32 >( ) , vec![ 10.0 , 20.0 , 30.0 ] ) ;
1805+ a *= & b;
1806+ assert_eq ! ( a. to_vec:: <f32 >( ) , vec![ 10.0 , 40.0 , 90.0 ] ) ;
1807+ a /= & b;
1808+ assert_eq ! ( a. to_vec:: <f32 >( ) , vec![ 10.0 , 20.0 , 30.0 ] ) ;
1809+
1810+ // The right-hand side is borrowed, so `b` survives all four.
1811+ assert_eq ! ( b. to_vec:: <f32 >( ) , vec![ 1.0 , 2.0 , 3.0 ] ) ;
1812+ }
1813+
1814+ #[ test]
1815+ fn assigning_operators_take_scalars ( ) {
1816+ let mut a = Array :: from_slice ( & [ 10.0f32 , 20.0 ] , & [ 2 ] ) ;
1817+ a += 5.0f32 ;
1818+ assert_eq ! ( a. to_vec:: <f32 >( ) , vec![ 15.0 , 25.0 ] ) ;
1819+ a *= 2.0f32 ;
1820+ assert_eq ! ( a. to_vec:: <f32 >( ) , vec![ 30.0 , 50.0 ] ) ;
1821+ a -= 10.0f32 ;
1822+ assert_eq ! ( a. to_vec:: <f32 >( ) , vec![ 20.0 , 40.0 ] ) ;
1823+ a /= 4.0f32 ;
1824+ assert_eq ! ( a. to_vec:: <f32 >( ) , vec![ 5.0 , 10.0 ] ) ;
1825+ }
1826+
1827+ #[ test]
1828+ fn assigning_operators_rebind_rather_than_mutate ( ) {
1829+ // Broadcasting means the result need not even have the same shape as the
1830+ // original, which an in-place op could not do.
1831+ let mut a = Array :: from_slice ( & [ 1.0f32 , 2.0 ] , & [ 2 ] ) ;
1832+ a += & Array :: from_slice ( & [ 10.0f32 , 20.0 , 30.0 , 40.0 ] , & [ 2 , 2 ] ) ;
1833+ assert_eq ! ( a. shape( ) , vec![ 2 , 2 ] ) ;
1834+ assert_eq ! ( a. to_vec:: <f32 >( ) , vec![ 11.0 , 22.0 , 31.0 , 42.0 ] ) ;
1835+
1836+ // And MLX's promotion applies, so the dtype can change under `a`.
1837+ let mut ints = Array :: from_slice ( & [ 1i32 , 2 ] , & [ 2 ] ) ;
1838+ assert_eq ! ( ints. dtype( ) , Dtype :: Int32 ) ;
1839+ ints *= 0.5f32 ;
1840+ assert_eq ! ( ints. dtype( ) , Dtype :: Float32 ) ;
1841+ assert_eq ! ( ints. to_vec:: <f32 >( ) , vec![ 0.5 , 1.0 ] ) ;
1842+ }
1843+
1844+ #[ test]
1845+ #[ should_panic( expected = "Array::subtract failed: MLX error:" ) ]
1846+ fn assigning_operator_panic_carries_mlx_message ( ) {
1847+ let mut a = Array :: from_slice ( & [ 1.0f32 , 2.0 , 3.0 ] , & [ 3 ] ) ;
1848+ a -= & Array :: from_slice ( & [ 1.0f32 , 2.0 ] , & [ 2 ] ) ;
1849+ }
1850+
17621851 #[ test]
17631852 #[ should_panic( expected = "Array::add failed: MLX error:" ) ]
17641853 fn operator_panic_carries_mlx_message ( ) {
0 commit comments