pub struct Scratch(/* private fields */);
Expand description
Mutable workspace required by all search methods.
This type also serves as the most general entry point to the various synchronous and asynchronous string search methods.
Implementations§
Source§impl Scratch
§Setup Methods
These methods create a new scratch space or initialize it against a
database. Database::allocate_scratch()
is also provided as a convenience
method to combine the creation and initialization steps.
impl Scratch
§Setup Methods
These methods create a new scratch space or initialize it against a
database. Database::allocate_scratch()
is also provided as a convenience
method to combine the creation and initialization steps.
Sourcepub fn setup_for_db(
&mut self,
db: &Database,
) -> Result<(), VectorscanRuntimeError>
pub fn setup_for_db( &mut self, db: &Database, ) -> Result<(), VectorscanRuntimeError>
Initialize this scratch space against the given db
.
A single scratch space can be initialized against multiple databases, but
exclusive mutable access is required to perform a search, so
Self::try_clone()
can be used to obtain multiple copies of a
multiply-initialized scratch space.
#[cfg(feature = "compiler")]
fn main() -> Result<(), vectorscan::error::VectorscanError> {
use vectorscan::{expression::*, flags::*, matchers::*, state::*, sources::*};
let a_expr: Expression = "a+".parse()?;
let a_db = a_expr.compile(Flags::SOM_LEFTMOST, Mode::BLOCK)?;
let b_expr: Expression = "b+".parse()?;
let b_db = b_expr.compile(Flags::SOM_LEFTMOST, Mode::BLOCK)?;
let mut scratch = Scratch::blank();
scratch.setup_for_db(&a_db)?;
scratch.setup_for_db(&b_db)?;
let s: ByteSlice = "ababaabb".into();
let mut matches: Vec<&str> = Vec::new();
scratch
.scan_sync(&a_db, s, |m| {
matches.push(unsafe { m.source.as_str() });
MatchResult::Continue
})?;
assert_eq!(&matches, &["a", "a", "a", "aa"]);
matches.clear();
scratch
.scan_sync(&b_db, s, |m| {
matches.push(unsafe { m.source.as_str() });
MatchResult::Continue
})?;
assert_eq!(&matches, &["b", "b", "b", "bb"]);
Ok(())
}
Source§impl Scratch
§Synchronous String Scanning
Vectorscan’s string search interface requires a C function pointer to call
synchronously upon each match. This guarantee of synchronous invocation
enables the function to mutate data under the expectation of exclusive
access (we formalize this guarantee as FnMut
). While Rust closures
cannot be converted into C function pointers automatically, vectorscan also
passes in a *mut c_void
context pointer to each invocation of the match
callback, and this can be used to hold a type-erased container for a
Rust-level closure.
impl Scratch
§Synchronous String Scanning
Vectorscan’s string search interface requires a C function pointer to call
synchronously upon each match. This guarantee of synchronous invocation
enables the function to mutate data under the expectation of exclusive
access (we formalize this guarantee as FnMut
). While Rust closures
cannot be converted into C function pointers automatically, vectorscan also
passes in a *mut c_void
context pointer to each invocation of the match
callback, and this can be used to hold a type-erased container for a
Rust-level closure.
§Ephemeral Match Objects
In all of these synchronous search methods, the provided match callback f
is converted into a dyn
reference and invoked within the C function
pointer provided to the vectorscan library. Match objects like Match
provided to the match callback are synthesized by this crate and are not
preserved after each invocation of f
, so the match callback must modify
some external state to store match results.
Sourcepub fn scan_sync<'data>(
&mut self,
db: &Database,
data: ByteSlice<'data>,
f: impl FnMut(Match<'data>) -> MatchResult,
) -> Result<(), VectorscanRuntimeError>
pub fn scan_sync<'data>( &mut self, db: &Database, data: ByteSlice<'data>, f: impl FnMut(Match<'data>) -> MatchResult, ) -> Result<(), VectorscanRuntimeError>
Synchronously scan a single contiguous string.
#[cfg(feature = "compiler")]
fn main() -> Result<(), vectorscan::error::VectorscanError> {
use vectorscan::{expression::*, flags::*, matchers::*, error::*};
let a_expr: Expression = "a+".parse()?;
let b_expr: Expression = "b+".parse()?;
let flags = Flags::SOM_LEFTMOST;
let expr_set = ExpressionSet::from_exprs([&a_expr, &b_expr])
.with_flags([flags, flags])
.with_ids([ExprId(1), ExprId(2)]);
let db = expr_set.compile(Mode::BLOCK)?;
let mut scratch = db.allocate_scratch()?;
let mut matches: Vec<&str> = Vec::new();
{
let mut f = |Match { source, .. }| {
matches.push(unsafe { source.as_str() });
MatchResult::Continue
};
scratch.scan_sync(&db, "aardvark".into(), &mut f)?;
scratch.scan_sync(&db, "imbibbe".into(), &mut f)?;
}
assert_eq!(&matches, &["a", "aa", "a", "b", "b", "bb"]);
let ret = scratch.scan_sync(&db, "abwuebiaubeb".into(), |_| MatchResult::CeaseMatching);
assert!(matches![ret, Err(VectorscanRuntimeError::ScanTerminated)]);
Ok(())
}
Sourcepub fn scan_sync_vectored<'data>(
&mut self,
db: &Database,
data: VectoredByteSlices<'data, 'data>,
f: impl FnMut(VectoredMatch<'data>) -> MatchResult,
) -> Result<(), VectorscanRuntimeError>
Available on crate feature vectored
only.
pub fn scan_sync_vectored<'data>( &mut self, db: &Database, data: VectoredByteSlices<'data, 'data>, f: impl FnMut(VectoredMatch<'data>) -> MatchResult, ) -> Result<(), VectorscanRuntimeError>
vectored
only.Synchronously scan a slice of vectored string data.
#[cfg(feature = "compiler")]
fn main() -> Result<(), vectorscan::error::VectorscanError> {
use vectorscan::{expression::*, flags::*, sources::*, matchers::*};
let a_plus: Expression = "a+".parse()?;
let b_plus: Expression = "b+".parse()?;
let asdf: Expression = "asdf(.)".parse()?;
let flags = Flags::SOM_LEFTMOST;
let expr_set = ExpressionSet::from_exprs([&a_plus, &b_plus, &asdf])
.with_flags([flags, flags, flags])
.with_ids([ExprId(0), ExprId(3), ExprId(2)]);
let db = expr_set.compile(Mode::VECTORED)?;
let mut scratch = db.allocate_scratch()?;
let data: [ByteSlice; 4] = [
"aardvark".into(),
"imbibbe".into(),
"leas".into(),
"dfeg".into(),
];
let mut matches: Vec<(u32, String)> = Vec::new();
scratch
.scan_sync_vectored(
&db,
data.as_ref().into(),
|VectoredMatch { id: ExpressionIndex(id), source, .. }| {
let joined = source.iter_slices()
.map(|s| unsafe { s.as_str() })
.collect::<Vec<_>>()
.concat();
matches.push((id, joined));
MatchResult::Continue
})?;
assert_eq!(matches, vec![
(0, "a".to_string()),
(0, "aa".to_string()),
(0, "a".to_string()),
(3, "b".to_string()),
(3, "b".to_string()),
(3, "bb".to_string()),
(0, "a".to_string()),
// NB: This match result crosses a slice boundary!
(2, "asdfe".to_string()),
]);
Ok(())
}
Note that if you do not need to know anything other than the offsets of
the matches, then you could also try using
Self::scan_sync_vectored_stream()
, which passes
StreamMatch
to the callback method
(although a subsequent
Scratch::flush_eod_sync()
call is necessary to match against end-of-stream markers; see
MatchAtEndBehavior
,
which is returned by
Expression::info()
).
Sourcepub fn scan_sync_stream<'data, 'code>(
&mut self,
live: &mut LiveStream,
matcher: &mut StreamMatcher<'code>,
data: ByteSlice<'data>,
) -> Result<(), VectorscanRuntimeError>
Available on crate feature stream
only.
pub fn scan_sync_stream<'data, 'code>( &mut self, live: &mut LiveStream, matcher: &mut StreamMatcher<'code>, data: ByteSlice<'data>, ) -> Result<(), VectorscanRuntimeError>
stream
only.Write data
into the stream object live
.
This method is mostly used internally; consumers of this crate will likely
prefer to call
ScratchStreamSink::scan()
.
Sourcepub fn scan_sync_vectored_stream<'data, 'code>(
&mut self,
live: &mut LiveStream,
matcher: &mut StreamMatcher<'code>,
data: VectoredByteSlices<'data, 'data>,
) -> Result<(), VectorscanRuntimeError>
Available on crate features stream
and vectored
only.
pub fn scan_sync_vectored_stream<'data, 'code>( &mut self, live: &mut LiveStream, matcher: &mut StreamMatcher<'code>, data: VectoredByteSlices<'data, 'data>, ) -> Result<(), VectorscanRuntimeError>
stream
and vectored
only.Write vectored data
into the stream object live
.
This method is mostly used internally; consumers of this crate will likely
prefer to call
ScratchStreamSink::scan_vectored()
.
Sourcepub fn flush_eod_sync<'code>(
&mut self,
live: &mut LiveStream,
matcher: &mut StreamMatcher<'code>,
) -> Result<(), VectorscanRuntimeError>
Available on crate feature stream
only.
pub fn flush_eod_sync<'code>( &mut self, live: &mut LiveStream, matcher: &mut StreamMatcher<'code>, ) -> Result<(), VectorscanRuntimeError>
stream
only.Process any EOD (end-of-data) matches for the stream object live
.
This method is mostly used internally; consumers of this crate will likely
prefer to call
ScratchStreamSink::flush_eod()
.
Source§impl Scratch
§Convenience Methods
These methods provide quick access to common regex use cases without needing
to provide a closure.
impl Scratch
§Convenience Methods
These methods provide quick access to common regex use cases without needing to provide a closure.
Sourcepub fn first_match<'data>(
&mut self,
db: &Database,
data: ByteSlice<'data>,
) -> Result<Option<Match<'data>>, VectorscanRuntimeError>
pub fn first_match<'data>( &mut self, db: &Database, data: ByteSlice<'data>, ) -> Result<Option<Match<'data>>, VectorscanRuntimeError>
Return the result from the first match callback, or None
if no match
was found.
#[cfg(feature = "compiler")]
fn main() -> Result<(), vectorscan::error::VectorscanError> {
use vectorscan::{expression::*, flags::*};
let expr: Expression = "a+".parse()?;
let db = expr.compile(Flags::SOM_LEFTMOST, Mode::BLOCK)?;
let mut scratch = db.allocate_scratch()?;
let msg = "aardvark";
let first_a = scratch.first_match(&db, msg.into())?.unwrap().source.as_slice();
assert_eq!(first_a, b"a");
assert_eq!(first_a.as_ptr(), msg.as_bytes().as_ptr());
Ok(())
}
Sourcepub fn full_match<'data>(
&mut self,
db: &Database,
data: ByteSlice<'data>,
) -> Result<Option<Match<'data>>, VectorscanRuntimeError>
pub fn full_match<'data>( &mut self, db: &Database, data: ByteSlice<'data>, ) -> Result<Option<Match<'data>>, VectorscanRuntimeError>
Return the first match result that matches the full length of the input
string, or None
if no match could be found.
#[cfg(feature = "compiler")]
fn main() -> Result<(), vectorscan::error::VectorscanError> {
use vectorscan::{expression::*, flags::*};
let expr: Expression = "a+sdf".parse()?;
let db = expr.compile(Flags::default(), Mode::BLOCK)?;
let mut scratch = db.allocate_scratch()?;
let msg = "asdf";
let m = scratch.full_match(&db, msg.into())?.unwrap().source.as_slice();
assert_eq!(m, msg.as_bytes());
assert_eq!(m.as_ptr(), msg.as_bytes().as_ptr());
Ok(())
}
Source§impl Scratch
§Asynchronous String Scanning
While the synchronous search methods can be used from async or
multi-threaded code, a multithreaded execution environment offers particular
opportunities to improve search latency and/or throughput. These methods
are written to expose an idiomatic Rust interface for highly parallel
searching.
impl Scratch
§Asynchronous String Scanning
While the synchronous search methods can be used from async or multi-threaded code, a multithreaded execution environment offers particular opportunities to improve search latency and/or throughput. These methods are written to expose an idiomatic Rust interface for highly parallel searching.
§Minimizing Work in the Match Callback
Because the vectorscan match callback is always invoked synchronously, it
also stops the regex engine from making any further progress while it
executes. If the match callback does too much work before returning control
to the vectorscan library, this may harm search performance by thrashing the
processor caches or other state.[FIXME: citation needed/benchmark this]
§Producer-Consumer Pattern
Therefore, one useful pattern is to write the match object to a queue and quickly exit the match callback, then read matches from the queue in another thread of control in order to decouple match processing from text searching. Multi-processor systems in particular may be able to achieve higher search throughput if a separate thread is used to perform further match processing in parallel while a vectorscan search is executing.
Note that the Synchronous String Scanning
API can still be used to implement
producer-consumer match queues! In fact, Self::scan_channel()
is
implemented just by writing to a queue within the match callback provided
to an internal Self::scan_sync()
call! However, async
streams provide
a natural interface to wrap the output of a queue, so the methods in this
section return a Stream
, which can be consumed or extended by external
code such as futures_util::TryStreamExt
.
§Match Objects with Lifetimes
The match callbacks for these methods accept a
reference to the match object, instead of owning the match result like the
Ephemeral Match Objects
from synchronous
search methods. Even though most match objects are Copy
anyway, this
reference interface is used to clarify that the callback should only
determine whether to continue matching, while the underlying match object
will be written into the returned stream and should be retrieved from there
instead for further processing.
Sourcepub fn scan_channel<'data>(
&mut self,
db: &Database,
data: ByteSlice<'data>,
f: impl FnMut(&Match<'data>) -> MatchResult + Send,
) -> impl Stream<Item = Result<Match<'data>, ScanError>>
Available on crate feature async
only.
pub fn scan_channel<'data>( &mut self, db: &Database, data: ByteSlice<'data>, f: impl FnMut(&Match<'data>) -> MatchResult + Send, ) -> impl Stream<Item = Result<Match<'data>, ScanError>>
async
only.Asynchronously scan a single contiguous string.
#[cfg(feature = "compiler")]
fn main() -> Result<(), vectorscan::error::VectorscanError> { tokio_test::block_on(async {
use vectorscan::{expression::*, flags::*, matchers::*, error::*};
use futures_util::TryStreamExt;
let a_expr: Expression = "a+".parse()?;
let b_expr: Expression = "b+".parse()?;
let flags = Flags::SOM_LEFTMOST;
let expr_set = ExpressionSet::from_exprs([&a_expr, &b_expr])
.with_flags([flags, flags])
.with_ids([ExprId(1), ExprId(2)]);
let db = expr_set.compile(Mode::BLOCK)?;
let mut scratch = db.allocate_scratch()?;
let matches: Vec<&str> = scratch
.scan_channel(&db, "aardvark".into(), |_| MatchResult::Continue)
.map_ok(|Match { source, .. }| unsafe { source.as_str() })
.try_collect()
.await?;
assert_eq!(&matches, &["a", "aa", "a"]);
let matches: Vec<&str> = scratch
.scan_channel(&db, "imbibbe".into(), |_| MatchResult::Continue)
.map_ok(|Match { source, .. }| unsafe { source.as_str() })
.try_collect()
.await?;
assert_eq!(&matches, &["b", "b", "bb"]);
let ret = scratch.scan_channel(
&db,
"abwuebiaubeb".into(),
|_| MatchResult::CeaseMatching,
).try_for_each(|_| async { Ok(()) })
.await;
assert!(matches![ret, Err(ScanError::ReturnValue(VectorscanRuntimeError::ScanTerminated))]);
Ok(())
})}
Sourcepub fn scan_channel_vectored<'data>(
&mut self,
db: &Database,
data: VectoredByteSlices<'data, 'data>,
f: impl FnMut(&VectoredMatch<'data>) -> MatchResult + Send,
) -> impl Stream<Item = Result<VectoredMatch<'data>, ScanError>>
Available on crate features async
and vectored
only.
pub fn scan_channel_vectored<'data>( &mut self, db: &Database, data: VectoredByteSlices<'data, 'data>, f: impl FnMut(&VectoredMatch<'data>) -> MatchResult + Send, ) -> impl Stream<Item = Result<VectoredMatch<'data>, ScanError>>
async
and vectored
only.Asynchronously scan a slice of vectored string data.
#[cfg(feature = "compiler")]
fn main() -> Result<(), vectorscan::error::VectorscanError> { tokio_test::block_on(async {
use vectorscan::{expression::*, flags::*, sources::*, matchers::*};
use futures_util::TryStreamExt;
let a_plus: Expression = "a+".parse()?;
let b_plus: Expression = "b+".parse()?;
let asdf: Expression = "asdf(.)".parse()?;
let flags = Flags::SOM_LEFTMOST;
let expr_set = ExpressionSet::from_exprs([&a_plus, &b_plus, &asdf])
.with_flags([flags, flags, flags])
.with_ids([ExprId(0), ExprId(3), ExprId(2)]);
let db = expr_set.compile(Mode::VECTORED)?;
let mut scratch = db.allocate_scratch()?;
let data: [ByteSlice; 4] = [
"aardvark".into(),
"imbibbe".into(),
"leas".into(),
"dfeg".into(),
];
let matches: Vec<(u32, String)> = scratch
.scan_channel_vectored(&db, data.as_ref().into(), |_| MatchResult::Continue)
.map_ok(|VectoredMatch { id: ExpressionIndex(id), source, .. }| {
let joined = source.iter_slices()
.map(|s| unsafe { s.as_str() })
.collect::<Vec<_>>()
.concat();
(id, joined)
})
.try_collect()
.await?;
assert_eq!(matches, vec![
(0, "a".to_string()),
(0, "aa".to_string()),
(0, "a".to_string()),
(3, "b".to_string()),
(3, "b".to_string()),
(3, "bb".to_string()),
(0, "a".to_string()),
// NB: This match result crosses a slice boundary!
(2, "asdfe".to_string()),
]);
Ok(())
})}
Source§impl Scratch
§Managing Allocations
These methods provide access to the underlying memory allocation
containing the data for the scratch space. They can be used to
control the memory location used for the scratch space, or to preserve
scratch allocations across weird lifetime constraints.
impl Scratch
§Managing Allocations
These methods provide access to the underlying memory allocation containing the data for the scratch space. They can be used to control the memory location used for the scratch space, or to preserve scratch allocations across weird lifetime constraints.
Note that Self::scratch_size()
can be used to determine the size of
the memory allocation pointed to by Self::as_ref_native()
and
Self::as_mut_native()
.
Sourcepub unsafe fn from_native(p: *mut NativeScratch) -> Self
pub unsafe fn from_native(p: *mut NativeScratch) -> Self
Wrap the provided allocation p
.
§Safety
The pointer p
must be null or have been produced by
Self::as_mut_native()
.
This method also makes it especially easy to create multiple references to
the same allocation, which will then cause a double free when
Self::try_drop()
is called more than once for the same scratch
allocation. To avoid this, wrap the result in a
ManuallyDrop
:
#[cfg(feature = "compiler")]
fn main() -> Result<(), vectorscan::error::VectorscanError> {
use vectorscan::{expression::*, flags::*, matchers::*, state::*};
use std::{mem::ManuallyDrop, ptr};
// Compile a legitimate db:
let expr: Expression = "a+".parse()?;
let db = expr.compile(Flags::SOM_LEFTMOST, Mode::BLOCK)?;
let mut scratch = db.allocate_scratch()?;
// Create two new references to that allocation,
// wrapped to avoid calling the drop code:
let scratch_ptr: *mut NativeScratch = scratch
.as_mut_native()
.map(|p| p as *mut NativeScratch)
.unwrap_or(ptr::null_mut());
let mut scratch_ref_1 = ManuallyDrop::new(unsafe { Scratch::from_native(scratch_ptr) });
let mut scratch_ref_2 = ManuallyDrop::new(unsafe { Scratch::from_native(scratch_ptr) });
// Both scratch references are valid and can be used for matching.
let mut matches: Vec<&str> = Vec::new();
scratch_ref_1
.scan_sync(&db, "aardvark".into(), |Match { source, .. }| {
matches.push(unsafe { source.as_str() });
MatchResult::Continue
})?;
scratch_ref_2
.scan_sync(&db, "aardvark".into(), |Match { source, .. }| {
matches.push(unsafe { source.as_str() });
MatchResult::Continue
})?;
assert_eq!(&matches, &["a", "aa", "a", "a", "aa", "a"]);
Ok(())
}
Sourcepub fn as_ref_native(&self) -> Option<&NativeScratch>
pub fn as_ref_native(&self) -> Option<&NativeScratch>
Get a read-only reference to the scratch allocation.
This method is mostly used internally and converted to a nullable pointer to provide to the vectorscan native library methods.
Sourcepub fn as_mut_native(&mut self) -> Option<&mut NativeScratch>
pub fn as_mut_native(&mut self) -> Option<&mut NativeScratch>
Get a mutable reference to the scratch allocation.
The result of this method can be converted to a nullable pointer and
provided to Self::from_native()
.
Sourcepub fn scratch_size(&self) -> Result<usize, VectorscanRuntimeError>
pub fn scratch_size(&self) -> Result<usize, VectorscanRuntimeError>
Return the size of the scratch allocation.
Using Flags::UCP
explodes the size of
character classes, which increases the size of the scratch space:
#[cfg(feature = "compiler")]
fn main() -> Result<(), vectorscan::error::VectorscanError> {
use vectorscan::{expression::*, flags::*};
let expr: Expression = r"\w".parse()?;
let utf8_db = expr.compile(Flags::UTF8 | Flags::UCP, Mode::BLOCK)?;
let ascii_db = expr.compile(Flags::default(), Mode::BLOCK)?;
let utf8_scratch = utf8_db.allocate_scratch()?;
let ascii_scratch = ascii_db.allocate_scratch()?;
// Including UTF-8 classes increases the size:
assert!(utf8_scratch.scratch_size()? > ascii_scratch.scratch_size()?);
Ok(())
}
This size corresponds to the requested allocation size passed to the scratch allocator:
#[cfg(all(feature = "alloc", feature = "compiler"))]
fn main() -> Result<(), vectorscan::error::VectorscanError> {
use vectorscan::{expression::*, flags::*, state::*, alloc::*};
use std::alloc::System;
// Wrap the standard Rust System allocator.
let tracker = LayoutTracker::new(System.into());
// Register it as the allocator for databases.
assert!(set_scratch_allocator(tracker)?.is_none());
let expr: Expression = r"\w".parse()?;
let utf8_db = expr.compile(Flags::UTF8 | Flags::UCP, Mode::BLOCK)?;
let mut utf8_scratch = utf8_db.allocate_scratch()?;
// Get the scratch allocator we just registered and view its live allocations:
let allocs = get_scratch_allocator().as_ref().unwrap().current_allocations();
// Verify that only the single known scratch was allocated:
assert_eq!(1, allocs.len());
let (p, layout) = allocs[0];
// The allocation was made within 0x30 bytes to the left of the returned scratch pointer.
let alloc_ptr = utf8_scratch.as_mut_native().unwrap() as *mut NativeScratch as *mut u8;
let p_diff = (alloc_ptr as usize) - (p.as_ptr() as usize);
assert!(p_diff <= 0x30);
// Verify that the allocation size is the same as reported:
assert_eq!(layout.size(), utf8_scratch.scratch_size()?);
Ok(())
}
Every single database of the same mode allocates the exact same scratch size:
#[cfg(feature = "compiler")]
fn main() -> Result<(), vectorscan::error::VectorscanError> {
use vectorscan::{expression::*, flags::*, state::*};
let a_expr: Expression = "a+".parse()?;
let b_expr: Expression = "b+".parse()?;
let a_db = a_expr.compile(Flags::default(), Mode::BLOCK)?;
let b_db = b_expr.compile(Flags::default(), Mode::BLOCK)?;
let a_scratch = a_db.allocate_scratch()?;
let b_scratch = b_db.allocate_scratch()?;
let mut abc_scratch = Scratch::blank();
abc_scratch.setup_for_db(&a_db)?;
abc_scratch.setup_for_db(&b_db)?;
assert_eq!(a_scratch.scratch_size()?, abc_scratch.scratch_size()?);
assert_eq!(a_scratch.scratch_size()?, b_scratch.scratch_size()?);
assert_eq!(b_scratch.scratch_size()?, abc_scratch.scratch_size()?);
let c_expr: Expression = "c{,2}|d?e+f?".parse()?;
let c_db = c_expr.compile(Flags::default(), Mode::BLOCK)?;
let c_scratch = c_db.allocate_scratch()?;
assert_eq!(a_scratch.scratch_size()?, c_scratch.scratch_size()?);
abc_scratch.setup_for_db(&c_db)?;
assert_eq!(a_scratch.scratch_size()?, abc_scratch.scratch_size()?);
Ok(())
}
However, databases of different modes allocate different scratch sizes:
#[cfg(all(feature = "compiler", feature = "vectored", feature = "stream"))]
fn main() -> Result<(), vectorscan::error::VectorscanError> {
use vectorscan::{expression::*, flags::*};
let expr: Expression = "a+".parse()?;
let block_db = expr.compile(Flags::default(), Mode::BLOCK)?;
let vectored_db = expr.compile(Flags::default(), Mode::VECTORED)?;
let stream_db = expr.compile(Flags::default(), Mode::STREAM)?;
let block_scratch = block_db.allocate_scratch()?;
let vectored_scratch = vectored_db.allocate_scratch()?;
let stream_scratch = stream_db.allocate_scratch()?;
assert!(block_scratch.scratch_size()? > stream_scratch.scratch_size()?);
assert!(block_scratch.scratch_size()? < vectored_scratch.scratch_size()?);
Ok(())
}
Scratch sizes do not change when used:
#[cfg(feature = "compiler")]
fn main() -> Result<(), vectorscan::error::VectorscanError> {
use vectorscan::{expression::*, flags::*, matchers::*};
let expr: Expression = "a+".parse()?;
let db = expr.compile(Flags::SOM_LEFTMOST, Mode::BLOCK)?;
let mut scratch = db.allocate_scratch()?;
let size = scratch.scratch_size()?;
let mut matches: Vec<&str> = Vec::new();
scratch
.scan_sync(&db, "aardvark".into(), |m| {
matches.push(unsafe { m.source.as_str() });
MatchResult::Continue
})?;
assert_eq!(&matches, &["a", "aa", "a"]);
assert_eq!(size, scratch.scratch_size()?);
Ok(())
}
Sourcepub fn try_clone(&self) -> Result<Self, VectorscanRuntimeError>
pub fn try_clone(&self) -> Result<Self, VectorscanRuntimeError>
Generate a new scratch space which can be applied to the same databases as the original.
This new scratch space is allocated in a new region of memory provided by
the scratch allocator. This is used to implement Clone
.
#[cfg(all(feature = "alloc", feature = "compiler"))]
fn main() -> Result<(), vectorscan::error::VectorscanError> {
use vectorscan::{expression::*, flags::*, alloc::*};
use std::alloc::System;
// Wrap the standard Rust System allocator.
let tracker = LayoutTracker::new(System.into());
// Register it as the allocator for databases.
assert!(set_scratch_allocator(tracker)?.is_none());
let expr: Expression = r"\w".parse()?;
let utf8_db = expr.compile(Flags::UTF8 | Flags::UCP, Mode::BLOCK)?;
let scratch1 = utf8_db.allocate_scratch()?;
let _scratch2 = scratch1.try_clone()?;
// Get the scratch allocator we just registered and view its live allocations:
let allocs = get_scratch_allocator().as_ref().unwrap().current_allocations();
// Verify that only two scratches were allocated:
assert_eq!(2, allocs.len());
let (p1, l1) = allocs[0];
let (p2, l2) = allocs[1];
assert!(p1 != p2);
assert!(l1 == l2);
Ok(())
}
Sourcepub unsafe fn try_drop(&mut self) -> Result<(), VectorscanRuntimeError>
pub unsafe fn try_drop(&mut self) -> Result<(), VectorscanRuntimeError>
Free the underlying scratch allocation.
§Safety
This method must be called at most once over the lifetime of each scratch
allocation. It is called by default on drop, so
ManuallyDrop
is recommended to wrap
instances that reference external data in order to avoid attempting to
free the referenced data.
Because the pointer returned by Self::as_mut_native()
does not
correspond to the entire scratch allocation, this method must be
executed in order to avoid leaking resources associated with a scratch
space. The memory must not be deallocated elsewhere.
Trait Implementations§
Source§impl Resource for Scratch
impl Resource for Scratch
Source§type Error = VectorscanRuntimeError
type Error = VectorscanRuntimeError
Source§fn deep_clone(&self) -> Result<Self, Self::Error>where
Self: Sized,
fn deep_clone(&self) -> Result<Self, Self::Error>where
Self: Sized,
self
.Source§fn deep_boxed_clone(
&self,
) -> Result<Box<dyn Resource<Error = Self::Error>>, Self::Error>
fn deep_boxed_clone( &self, ) -> Result<Box<dyn Resource<Error = Self::Error>>, Self::Error>
Self::deep_clone()
, but places the concrete type in a
heap-allocated box. Read moreimpl Send for Scratch
Auto Trait Implementations§
impl Freeze for Scratch
impl RefUnwindSafe for Scratch
impl !Sync for Scratch
impl Unpin for Scratch
impl UnwindSafe for Scratch
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<R> Handle for Rwhere
R: Resource,
impl<R> Handle for Rwhere
R: Resource,
Source§type R = R
type R = R
Source§fn wrap(r: <R as Handle>::R) -> R
fn wrap(r: <R as Handle>::R) -> R
Source§fn clone_handle(&self) -> Result<R, <<R as Handle>::R as Resource>::Error>
fn clone_handle(&self) -> Result<R, <<R as Handle>::R as Resource>::Error>
Source§fn boxed_clone_handle(
&self,
) -> Result<Box<dyn Handle<R = <R as Handle>::R>>, <<R as Handle>::R as Resource>::Error>
fn boxed_clone_handle( &self, ) -> Result<Box<dyn Handle<R = <R as Handle>::R>>, <<R as Handle>::R as Resource>::Error>
Self::clone_handle()
, but places the concrete type in a
heap-allocated box.Source§fn handle_ref(&self) -> &<R as Handle>::R
fn handle_ref(&self) -> &<R as Handle>::R
Source§fn ensure_exclusive(
&mut self,
) -> Result<(), <<R as Handle>::R as Resource>::Error>
fn ensure_exclusive( &mut self, ) -> Result<(), <<R as Handle>::R as Resource>::Error>
Source§fn eq_ref(&self, other: &(dyn Handle<R = Self::R> + 'static)) -> bool
fn eq_ref(&self, other: &(dyn Handle<R = Self::R> + 'static)) -> bool
Source§fn make_mut(&mut self) -> Result<&mut Self::R, <Self::R as Resource>::Error>
fn make_mut(&mut self) -> Result<&mut Self::R, <Self::R as Resource>::Error>
Self::ensure_exclusive()
and Self::get_mut()
.