이번 편은 코드만 나열되고 새로 배울 건 없다.
오늘은 아래와 같은 파이썬 코드와 비슷한 코드를 러스트에서도 보는 날이다.
a = sum(range(100)[1::2][:12])print(a)
b = "sum(range(100)[1::2][:12])"[4:-6][::-1]
print(b)
144
]2::1[)001(egnar
기대가 된다. 위 같은 코드가 과연 러스트에서는 어떤 꼴이 될까?
"sum(range(100)[1::2][:12])"[4:-6][::-1]/* 가장 예쁘지만 불가능. Rust의 Range expressions을 사용해
(start..stop, step) 꼴을 인자로 받도록 만들어 보자. */
"sum(range(100)[1::2][:12])"[(4..-6,)][(.., -1)]
/* 불가능하다. Index는 self consume하지 않고,
&self와 같은 수명의 참조자만 반환할 수 있다. */
"sum(range(100)[1::2][:12])".pipe() | (4..-6) | (.., -1)
/* c++20의 view pipeline을 닮은 형식은 예쁘지만
연산자 오버로딩에서 orphan rule을 피하려면 귀찮게 새 타입을 만들어야 한다. */
"sum(range(100)[1::2][:12])".slice(4..-6).slice((.., -1))
/* 자바스크립트 형식은 예쁘다. 이걸로 하자. */
마지막 꼴을 만들기 위해 새로운 반복자 타입 Slice<T>을 만든다. 이 타입은 start, stop, step을 받아서 알맞게 아이템을 주는 역할을 한다. Slice<T>의 생성자는 아래 모습처럼 skip, take, step_by를 활용해서 만들 것이다. 그럼 아래 코드처럼 나올 것이다.
#[derive(Clone, Debug)]pub struct Slice<T> {
iter: ??????
}
impl<T> Slice<T> where
T : DoubleEndedIterator + ExactSizeIterator
{
pub fn new(mut iter: T, start: Option<isize>, stop: Option<isize>, step: Option<isize>) -> Self {
// 구현 생략
let new_iter;
if step > 0 {
new_iter = iter.take(take_n).skip(skip_n).step_by(step as usize);
} else {
new_iter = iter.rev().take(take_n).skip(skip_n).step_by(-step as usize);
}
Slice{iter: new_iter}
}
}
위 코드는 문제가 있다. new_iter의 타입 될 수 있는 iter.take(take_n).skip(skip_n).step_by(step as usize)와 iter.rev().take(take_n).skip(skip_n).step_by(step as usize)의 타입이 다르다. 전자는 StepBy<Skip<Take<T>>> 이고 후자는 StepBy<Skip<Take<Rev<T>>>>이기 때문이다. 이 두 타입을 동시에 다루기 위해서 enum 으로 EitherIter 타입을 만들어주자.
// use std::iter::{Iterator, DoubleEndedIterator, ExactSizeIterator};
#[derive(Clone, Debug)]
pub enum EitherIter<T, U> {
A(T), B(U)
}
impl<T, U, V> Iterator for EitherIter<T, U> where
T : Iterator<Item=V>, U : Iterator<Item=V>
{
type Item = V;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.nth(0)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
match self {
Self::A(ref a) => a.size_hint(),
Self::B(ref b) => b.size_hint()
}
}
#[inline]
fn nth(&mut self, n: usize) -> Option<Self::Item> {
match self {
Self::A(ref mut a) => a.nth(n),
Self::B(ref mut b) => b.nth(n)
}
}
}
impl<T, U, V> DoubleEndedIterator for EitherIter<T, U> where
T : Iterator<Item=V> + DoubleEndedIterator,
U : Iterator<Item=V> + DoubleEndedIterator
{
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
self.nth_back(0)
}
#[inline]
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
match self {
Self::A(ref mut a) => a.nth_back(n),
Self::B(ref mut b) => b.nth_back(n)
}
}
}
impl<T, U, V> ExactSizeIterator for EitherIter<T, U> where
T : Iterator<Item=V> + ExactSizeIterator,
U : Iterator<Item=V> + ExactSizeIterator {}
이제 이것을 이용해서 Slice<T>를 구현하면 아래와 같다.
use std::iter::{Take, Skip, StepBy, Rev};#[derive(Clone, Debug)]
pub struct Slice<T> {
iter: EitherIter<StepBy<Skip<Take<T>>>, StepBy<Skip<Take<Rev<T>>>>>
}
impl<T> Slice<T> where
T : DoubleEndedIterator + ExactSizeIterator
{
pub fn new(mut iter: T, start: Option<isize>, stop: Option<isize>, step: Option<isize>) -> Self {
assert!(step != Some(0), "step cannot be 0!");
let len = iter.len() as isize;
let step = step.unwrap_or(1);
// new slice is lazy
use std::cmp::max;
let new_iter;
if step > 0 {
let skip_n = start.map(|s| if s > 0 {s} else {max(0, s+len)}).unwrap_or(0) as usize;
let take_n = stop.map(|s| if s > 0 {s} else {max(0, s+len)}).unwrap_or(len) as usize;
new_iter = EitherIter::A( iter.take(take_n).skip(skip_n).step_by(step as usize) );
} else {
let skip_n = start.map(|s| if s > 0 {max(0, len-s-1)} else {-s-1}).unwrap_or(0) as usize;
let take_n = stop.map(|s| if s > 0 {max(0, len-s-1)} else {-s-1}).unwrap_or(len) as usize;
new_iter = EitherIter::B( iter.rev().take(take_n).skip(skip_n).step_by(-step as usize) );
}
Slice{iter: new_iter}
}
}
impl<T> Iterator for Slice<T> where
T : DoubleEndedIterator + ExactSizeIterator
{
type Item = <T as Iterator>::Item;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.iter.next()
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}
#[inline]
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.iter.nth(n)
}
}
impl<T> DoubleEndedIterator for Slice<T> where
T : DoubleEndedIterator + ExactSizeIterator
{
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
self.iter.next_back()
}
#[inline]
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
self.iter.nth_back(n)
}
}
impl<T> ExactSizeIterator for Slice<T> where T : DoubleEndedIterator + ExactSizeIterator {}
이제 반복자 뒤에 .slice(1..4) 처럼 붙일 수 있게 트레잇을 만든다. 이때 저번에 다룬 트레잇을 이용한 타입 오버로딩이 사용된다.
pub trait IterIntoSlice<A> : DoubleEndedIterator + ExactSizeIterator + Sized
{
fn slice(self, arg: A) -> Slice<Self>;
}
impl<A> IterIntoSlice<(Range<isize>,)> for A where
A : DoubleEndedIterator + ExactSizeIterator + Sized
{
#[inline]
fn slice(self, arg: (Range<isize>,)) -> Slice<Self> {
Slice::new(self, arg.0.start.into(), arg.0.end.into(), None)
}
}
impl<A> IterIntoSlice<Range<isize>> for A where
A : DoubleEndedIterator + ExactSizeIterator + Sized
{
#[inline]
fn slice(self, arg: Range<isize>,) -> Slice<Self> {
self.slice((arg,))
}
}
impl<A> IterIntoSlice<(RangeFrom<isize>,)> for A where
A : DoubleEndedIterator + ExactSizeIterator + Sized
{
#[inline]
fn slice(self, arg: (RangeFrom<isize>,)) -> Slice<Self> {
Slice::new(self, arg.0.start.into(), None, None)
}
}
impl<A> IterIntoSlice<RangeFrom<isize>> for A where
A : DoubleEndedIterator + ExactSizeIterator + Sized
{
#[inline]
fn slice(self, arg: RangeFrom<isize>) -> Slice<Self> {
self.slice((arg,))
}
}
impl<A> IterIntoSlice<(RangeFull,)> for A where
A : DoubleEndedIterator + ExactSizeIterator + Sized
{
#[inline]
fn slice(self, _arg: (RangeFull,)) -> Slice<Self> {
Slice::new(self, None, None, None)
}
}
impl<A> IterIntoSlice<RangeFull> for A where
A : DoubleEndedIterator + ExactSizeIterator + Sized
{
#[inline]
fn slice(self, arg: RangeFull) -> Slice<Self> {
self.slice((arg,))
}
}
impl<A> IterIntoSlice<(RangeTo<isize>,)> for A where
A : DoubleEndedIterator + ExactSizeIterator + Sized
{
#[inline]
fn slice(self, arg: (RangeTo<isize>,)) -> Slice<Self> {
Slice::new(self, None, arg.0.end.into(), None)
}
}
impl<A> IterIntoSlice<RangeTo<isize>> for A where
A : DoubleEndedIterator + ExactSizeIterator + Sized
{
#[inline]
fn slice(self, arg: RangeTo<isize>) -> Slice<Self> {
self.slice((arg,))
}
}
impl<A> IterIntoSlice<(Range<isize>, isize)> for A where
A : DoubleEndedIterator + ExactSizeIterator + Sized
{
#[inline]
fn slice(self, arg: (Range<isize>, isize)) -> Slice<Self> {
Slice::new(self, arg.0.start.into(), arg.0.end.into(), arg.1.into())
}
}
impl<A> IterIntoSlice<(RangeFrom<isize>, isize)> for A where
A : DoubleEndedIterator + ExactSizeIterator + Sized
{
#[inline]
fn slice(self, arg: (RangeFrom<isize>, isize)) -> Slice<Self> {
Slice::new(self, arg.0.start.into(), None, arg.1.into())
}
}
impl<A> IterIntoSlice<(RangeFull, isize)> for A where
A : DoubleEndedIterator + ExactSizeIterator + Sized
{
#[inline]
fn slice(self, arg: (RangeFull, isize)) -> Slice<Self> {
Slice::new(self, None, None, arg.1.into())
}
}
impl<A> IterIntoSlice<(RangeTo<isize>, isize)> for A where
A : DoubleEndedIterator + ExactSizeIterator + Sized
{
#[inline]
fn slice(self, arg: (RangeTo<isize>, isize)) -> Slice<Self> {
Slice::new(self, None, arg.0.end.into(), arg.1.into())
}
}
슬라이스 [T] 뒤에나 문자열 슬라이스 str 뒤에도 붙을 수 있도록 트레잇을 만들면 아래 코드가 된다.
pub trait IntoSlice<'a> { type Input;
fn slice<A>(&'a self, arg: A) -> Slice<Self::Input> where
Self::Input : IterIntoSlice<A>;
}
impl<'a, T: 'a> IntoSlice<'a> for [T] {
type Input = Iter<'a, T>;
fn slice<A>(&'a self, arg: A) -> Slice<Self::Input> where
Self::Input : IterIntoSlice<A>
{
self.iter().slice(arg)
}
}
impl<'a> IntoSlice<'a> for str
{
type Input = Exact<Chars<'a>>;
fn slice<A>(&'a self, arg: A) -> Slice<Self::Input> where
Self::Input : IterIntoSlice<A>
{
self.chars().exact().slice(arg)
}
}
마지막으로 오버로딩편에서 만들었던 range함수도 DoubleEndedIterator+ExactSizeIterator 를 가질 수 있도록 새로 만들어주자.
struct RangeArg(isize, isize, isize);
impl From<isize> for RangeArg {
fn from(arg: isize) -> Self {
RangeArg(0, arg, 1)
}
}
impl From<(isize,)> for RangeArg {
fn from(arg: (isize,)) -> Self {
RangeArg(0, arg.0, 1)
}
}
impl From<(isize,isize)> for RangeArg {
fn from(arg: (isize,isize)) -> Self {
RangeArg(arg.0, arg.1, 1)
}
}
impl From<(isize,isize,isize)> for RangeArg {
fn from(arg: (isize,isize,isize)) -> Self {
RangeArg(arg.0, arg.1, arg.2)
}
}
use std::{ops::Range, iter::StepBy};
pub fn range<A: Into<RangeArg>>(arg: A) -> EitherIter<StepBy<Range<isize>>, StepBy<Rev<Range<isize>>>> {
let RangeArg(start, stop, step) = arg.into();
assert!(step != 0, "step cannot be 0!");
if step > 0 {
EitherIter::A( (start..stop).step_by(step as usize) )
} else {
EitherIter::B( (stop+(match (start-stop)%-step {0=>-step, i=>i})..start+1).rev().step_by(-step as usize) )
}
}
이제 모든 것이 준비되었다. 처음 본 파이썬 코드처럼 러스트 코드를 작성해보자.
fn main() { let a = range(100).slice((1.., 2)).slice(..12).sum::<isize>();
println!("{a}");
let b = "sum(range(100)[1::2][:12])".slice(4..-6).slice((.., -1)).collect::<String>();
println!("{b}");
}
144
]2::1[)001(egnar
이렇게 러스트는 더이상 파이썬이 부럽지 않다. 모든 문제는 해결되었다.
댓글 0