strings_set_file 0.1.0

Store a string in a file. Use mmap, optimize fragments on delete, and reference identical strings.
Documentation
use file_mmap::FileMmap;

#[repr(C)]
pub struct WordAddress{
    offset:i64
    ,len:u64
}
impl std::fmt::Debug for WordAddress {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(
            f
            ,"[ len:{} , addr:{} ]"
            ,self.len
            ,self.offset
        )
    }
}
impl Copy for WordAddress {}
impl std::clone::Clone for WordAddress {
    fn clone(&self) -> WordAddress {
        *self
    }
}
impl std::default::Default for WordAddress{
    fn default() -> WordAddress {
        WordAddress{
            len:0
            ,offset:0
        }
    }
}
impl WordAddress{
    pub fn offset(&self)->i64{
        self.offset
    }
}

struct FragmentGetResult{
    fragment_id:u64
    ,string_addr:u64
}
struct Fragment{
    filemmap:FileMmap
    ,list: *mut WordAddress
    ,record_count:u64
}
impl Fragment{
    pub fn new(path:&str) -> Result<Fragment,std::io::Error>{
        let init_size=std::mem::size_of::<WordAddress>() as u64;
        let filemmap=FileMmap::new(path,init_size)?;
        let list=filemmap.as_ptr() as *mut WordAddress;
        let len=filemmap.len() as u64;
        let mut record_count=if len==init_size{
            0
        }else{
            (len-init_size)/std::mem::size_of::<WordAddress>() as u64 - 1
        };
        if record_count>0{
            for i in -(record_count as i64)..0{
                let index=(-i) as u64;
                if unsafe{*list.offset(index as isize)}.offset==0{
                    record_count=index-1;
                }
            }
        }
        Ok(Fragment{
            filemmap
            ,list
            ,record_count
        })
    }
    pub fn add(&mut self,ystr:&WordAddress)->Result<u64,std::io::Error>{
        self.record_count+=1;
        let size=
            (std::mem::size_of::<WordAddress>() as u64)*(1+self.record_count)
        ;
        if self.filemmap.len()<size{
            self.filemmap.set_len(size as u64)?;
        }
        unsafe{
            *self.list.offset(self.record_count as isize)=*ystr;
        }
        Ok(self.record_count)
    }
    pub fn release(&mut self,id:u64,len:u64){
        let mut s=unsafe{
            &mut *self.list.offset(id as isize)
        };
        s.offset+=len as i64;
        s.len-=len;

        if s.len==0 && id==self.record_count{
            self.record_count-=1;
        }
    }
    pub fn search_blank(&self,len:u64)->Option<FragmentGetResult>{
        if self.record_count==0{
            None
        }else{
            for i in -(self.record_count as i64)..0{
                let index=(-i) as u64;
                let s=unsafe{
                    &mut *self.list.offset(index as isize)
                };
                if s.len>=len{
                    return Some(FragmentGetResult{
                        fragment_id:index
                        ,string_addr:s.offset as u64
                    });
                }
            }
            None
        }
    }
}

pub struct Word<'a>{
    address:WordAddress
    ,set:&'a StringsSetFile
}
impl ToString for Word<'_>{
    fn to_string(&self)->String {
        String::from(self.to_str())
    }
}
impl Word<'_>{
    pub fn to_str(&self)->&str{
        let offset=self.set.offset(self.address.offset as isize) as *mut i8;
        if let Ok(str)=unsafe{std::ffi::CStr::from_ptr(offset)}.to_str(){
            str
        }else{
            ""
        }
    }
    pub fn address_offset(&self)->i64{
        self.address.offset
    }
    pub fn address(&self)->WordAddress{
        self.address
    }
}

pub struct StringsSetFile{
    filemmap:FileMmap
    ,fragment:Fragment
}
impl StringsSetFile{
    pub fn new(path:&str) -> Result<StringsSetFile,std::io::Error>{
        let filemmap=FileMmap::new(path,1)?;
        let fragment=Fragment::new(&(path.to_string()+".f"))?;
        Ok(StringsSetFile{
            filemmap
            ,fragment
        })
    }
    pub fn to_str(&self,word:&WordAddress)->&str {
        let offset=self.offset(word.offset() as isize) as *mut i8;
        if let Ok(str)=unsafe{std::ffi::CStr::from_ptr(offset)}.to_str(){
            str
        }else{
            ""
        }
    }
    pub fn offset(&self,addr:isize)->*const i8{
        self.filemmap.offset(addr)
    }
    pub fn insert(&mut self,target:&str)->Option<Word>{
        let len=target.len() as u64;
        match self.fragment.search_blank(len){
            Some(r)=>{
                self.filemmap.write(r.string_addr,(target.to_string()+"\0").as_bytes());
                self.fragment.release(r.fragment_id,len);
                Some(Word{
                    address:WordAddress{offset:r.string_addr as i64,len}
                    ,set:self
                })
            }
            ,None=>{
                if let Some(addr)=self.filemmap.append((target.to_string()+"\0").as_bytes()){
                    Some(Word{
                        address:WordAddress{offset:addr as i64,len}
                        ,set:self
                    })
                }else{
                    None
                }
            }
        }
    }
    pub fn remove(&mut self,ystr:&WordAddress){
        self.filemmap.write_0(ystr.offset as isize,ystr.len);
        self.fragment.add(ystr).unwrap();
    }
}

#[test]
fn test(){
    if let Ok(mut s)=StringsSetFile::new("D:\\test.str"){
        if let Some(w)=s.insert("TEST"){
            assert_eq!("TEST".to_string(),w.to_string());
        }
        if let Some(w)=s.insert("HOGE"){
            assert_eq!("HOGE".to_string(),w.to_string());
        }
        if let Some(w)=s.insert("TEST"){
            assert_eq!("TEST".to_string(),w.to_string());
        }
    }
}