As a purely pedagogical exercise, I've been trying to use Rust's (very expressive) type system to define the bare minimum one might expect from an ordinary list type.
While there's likely a higher-level argument here about mutable vs immutable lists, and how lists should be implemented in Rust, please note that this is purely an educational exercise.
One thing I've encountered, is that Rust lends itself to quite some verbosity with respect to the number of different logical blocks of code one might need in order to comply with prevailing conventions. I'm unsure as to whether this is actually true of Rust itself, or due to my own misunderstandings about Rust as a language and as an ecosystem.
list.rs
:
use std::fmt::Debug;
use std::ops::{Index, IndexMut};
pub trait List<T: Sized + Clone + Eq + Debug>:
Eq + Index<usize> + IndexMut<usize> + IntoIterator + Debug
{
type Error;
fn insert(&mut self, pos: usize, elem: T) -> Result<(), Self::Error>;
fn remove(&mut self, pos: usize) -> Result<T, Self::Error>;
fn length(&self) -> usize;
fn contains(&self, elem: T) -> bool;
}
veclist.rs
:
#![allow(clippy::unit_arg)]
use std::cmp::Ordering;
use std::fmt::Debug;
use std::ops::{Index, IndexMut};
use crate::list::List;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VecList<T: Clone + Debug + Eq> {
pub elems: Vec<T>,
}
impl<T: Clone + Debug + Eq> List<T> for VecList<T> {
type Error = String;
fn insert(&mut self, pos: usize, elem: T) -> Result<(), Self::Error> {
match pos.cmp(&self.elems.len()) {
Ordering::Less => Ok(self.elems.insert(pos, elem)),
Ordering::Equal => Ok(self.elems.push(elem)),
Ordering::Greater => Err("Out of bounds".to_string()),
}
}
fn remove(&mut self, pos: usize) -> Result<T, Self::Error> {
match pos.cmp(&self.elems.len()) {
Ordering::Less => Ok(self.elems.remove(pos)),
_ => Err("Out of bounds".to_string()),
}
}
fn length(&self) -> usize {
self.elems.len()
}
fn contains(&self, elem: T) -> bool {
self.iter().any(|x| x == elem)
}
}
impl<T: Clone + Debug + Eq> VecList<T> {
pub fn iter(&self) -> VecListIterator<T> {
self.into_iter()
}
}
impl<T: Clone + Debug + Eq> Index<usize> for VecList<T> {
type Output = T;
fn index(&self, pos: usize) -> &Self::Output {
&self.elems[pos]
}
}
impl<T: Clone + Debug + Eq> IndexMut<usize> for VecList<T> {
fn index_mut(&mut self, pos: usize) -> &mut Self::Output {
&mut self.elems[pos]
}
}
pub struct VecListIterator<T: Clone + Debug + Eq> {
pos: usize,
list: VecList<T>,
}
impl<T: Clone + Debug + Eq> Iterator for VecListIterator<T> {
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
if self.pos < self.list.length() {
self.pos += 1;
Some(self.list[self.pos - 1].clone())
} else {
None
}
}
}
impl<T: Clone + Debug + Eq> IntoIterator for VecList<T> {
type Item = T;
type IntoIter = VecListIterator<T>;
fn into_iter(self) -> Self::IntoIter {
VecListIterator { pos: 0, list: self }
}
}
impl<T: Clone + Debug + Eq> IntoIterator for &VecList<T> {
type Item = T;
type IntoIter = VecListIterator<T>;
fn into_iter(self) -> Self::IntoIter {
VecListIterator {
pos: 0,
list: self.clone(),
}
}
}
impl<T: Clone + Debug + Eq> IntoIterator for &mut VecList<T> {
type Item = T;
type IntoIter = VecListIterator<T>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
- Is the definition of the list type,
List<T>
, sensible? - Is the implementation of the list type,
VecList<T>
, sensible? - Are there any defects in the codebase itself?