36
loading...
This website collects cookies to deliver better user experience
public abstract class FormulaPaserBase
{
protected abstract FormulaExpression ParseBinaryOperation(int index, List<Token> tokenstream);
public FormulaExpression Parse(int index, List<Token> tokenstream)
{
//Do something
var binaryExpression = ParseBinaryOperation(opertorIndex, tokenstream);
//Do more stuff
}
protected FormulaExpression ParseOperand(int index, List<Token> tokenstream)
{
//Do something
}
}
ParseBinaryOperation
is put onto a separate interface and objects implementing the interface are injected into the the base class. In C#, the base class might look as follows.public class FormulaPaser
{
private IBinaryOpParser _binaryOpParser;
public FormulaPaser(IBinaryOpParser binaryOpParser)
{
_binaryOpParser = binaryOpParser
}
public FormulaExpression Parse(int index, List<Token> tokenstream)
{
//Do something
var binaryExpression = ParseBinaryOperation(opertorIndex, tokenstream);
var binaryExpression =
//Do more stuff
}
public FormulaExpression ParseOperand(int index, List<Token> tokenstream)
{
//Do something
}
private FormulaExpression ParseBinaryOperation(int index, List<Token> tokenstream){
return _binaryOpParser.ParseBinaryOperation(opertorIndex, tokenstream);
}
}
IBinaryOpParser
will want to access the methods on the base class. This can be achieved in different ways. We will start with the more natural approach coming from C# and modify it until it actually works in Rust. Then we approach the issue in a slightly different way, leading to a more elegant and versatile solution. pub trait BinaryOpParsingStrategy{
fn parse_binary_op(&self, operator_index: usize, tokenstream: &[Token])-> Option<FormulaContext>;
}
Option
here to deal with inadequate inputs. A better approach might be do use Result
with appropriate errors, but we want to keep things simple here.pub struct FormulaParser<T>{
binary_op_parsing_strategy: T,
}
impl<T: BinaryOpParsingStrategy> FormulaParser<T>{
pub fn new(binary_op_parsing_strategy: T) -> FormulaParser<T>{
FormulaParser {binary_op_parsing_strategy}
}
pub fn parse(&self, index: usize, tokenstream: &[Token]) -> Option<FormulaContext>{
//Do something
let binary_expression = self.parse_binary_op(operator_index, tokenstream)?;
//Do more stuff
}
fn parse_operand(&self, index: usize, tokenstream: &[Token]) -> Option<FormulaExpression>
{
//Do something
}
fn parse_binary_op(&self, operator_index: usize, tokenstream: &[Token])-> Option<FormulaContext>{
self.binary_op_parsing_strategy.parse_binary_op(operator_index, tokenstream)
}
}
BinaryOpParsingStrategy
such that they are able to use the appropriate methods on the base struct. pub trait FormulaParserProtectedInterface{
fn parse(&self, index: usize, tokenstream: &[Token]) -> Option<FormulaContext>;
fn parse_operand(&self, index: usize, tokenstream: &[Token]) -> Option<FormulaExpression>;
fn parse_binary_op(&self, operator_index: usize, tokenstream: &[Token])-> Option<FormulaContext>;
}
impl<T: BinaryOpParsingStrategy> FormulaParserProtectedInterface for FormulaParser<T>{
fn parse(&self, index: usize, tokenstream: &[Token]) -> Option<FormulaContext>{
self.parse(index, tokenstream)
}
fn parse_operand(&self, index: usize, tokenstream: &[Token]) -> Option<FormulaExpression>{
self.parse_operand(index, tokenstream)
}
fn parse_binary_op(&self, operator_index: usize, tokenstream: &[Token])-> Option<FormulaContext>{
self.parse_binary_op(operator_index, tokenstream)
}
}
pub struct BinaryOpParser<T>{
base_parser: T,
}
impl<T: FormulaParserProtectedInterface> BinaryOpParsingStrategy for BinaryOpParser<T>{
fn parse_binary_op(&self, operator_index: usize, tokenstream: &[Token])-> Option<FormulaContext>{
//Do something
let operand = self.base_parser.parse_operand(operand_index, tokenstream);
//Do more stuff
}
}
impl<T: FormulaParserProtectedInterface> BinaryOpParser<T>{
pub fn new(base_parser: T) -> BinaryOpParser<T>{
BinaryOpParser {base_parser}
}
}
base_parser
field in the constructor and filled it in later. However, Rust does not allow this. Instead, we have to make it explicit that the field is optional.pub struct BinaryOpParser<T>{
base_parser: Option<T>,
}
impl<T: FormulaParserProtectedInterface> BinaryOpParsingStrategy for BinaryOpParser<T>{
fn parse_binary_op(&self, operator_index: usize, tokenstream: &[Token])-> Option<FormulaContext>{
let base_parser = self.base_parser?;
//Do something
let operand = self.base_parser.parse_operand(operand_index, tokenstream);
//Do more stuff
}
}
impl<T: FormulaParserProtectedInterface> BinaryOpParser<T>{
pub fn new(base_parser: T) -> BinaryOpParser<T>{
BinaryOpParser {None}
}
}
pub trait BaseStructInitializable<T>{
fn initialize_base(&mut self, base: T);
}
impl<T: FormulaParserProtectedInterface> BaseStructInitializable<T> for BinaryOpParser<T>{
fn initialize_base(&mut self, base: T){
if self.base_parser.is_none(){
self.base_parser = Some(base);
}
}
}
impl<T: BinaryOpParsingStrategy + BaseStructInitializable<Self>> FormulaParser<T>{
pub fn new(binary_op_parsing_strategy: T) -> FormulaParser<T>{
let parser = FormulaParser {binary_op_parsing_strategy};
binary_op_parsing_strategy.initialize_base(parser);
parser
}
}
binary_op_parsing_strategy
has been moved into parser
and can no longer be accessed when we initialize its field holding the base struct. As a fix, we might try the following.impl<T: BinaryOpParsingStrategy + BaseStructInitializable<Self>> FormulaParser<T>{
pub fn new(binary_op_parsing_strategy: T) -> FormulaParser<T>{
let parser = FormulaParser {binary_op_parsing_strategy};
parser.binary_op_parsing_strategy.initialize_base(parser);
parser
}
}
parser
from new
since we have moved it into binary_op_parsing_strategy
when we initialized the field holding the base struct.pub struct BinaryOpParser<a', T>{
base_parser: Option<&'a T>,
}
pub trait BaseStructInitializable<'a, T>{
fn initialize_base(&mut self, base: &'a T);
}
impl<'a, T: FormulaParserProtectedInterface> BaseStructInitializable<'a, T> for BinaryOpParser<T>{
fn initialize_base(&mut self, base: &'a T){
if self.base_parser.is_none(){
self.base_parser = Some(base);
}
}
}
impl<T: BinaryOpParsingStrategy + BaseStructInitializable<Self>> FormulaParser<T>{
pub fn new(binary_op_parsing_strategy: T) -> FormulaParser<T>{
let parser = FormulaParser {binary_op_parsing_strategy};
parser.binary_op_parsing_strategy.initialize_base(&parser);
parser
}
}
initialize_base
might not live long enough. But why is this the case?parser
is returned from new
. This is what the borrow checker guards against.Pin
, which prevents in-memory movement. Rc<T>
. Using it, our strategy looks as follows. (You have to import std::rc::Rc
.)pub struct BinaryOpParser<T>{
base_parser: Option<Rc<T>>,
}
pub trait BaseStructInitializable<T>{
fn initialize_base(&mut self, base: Rc<T>);
}
impl<T: FormulaParserProtectedInterface> BaseStructInitializable<T> for BinaryOpParser<T>{
fn initialize_base(&mut self, base: Rc<T>){
if self.base_parser.is_none(){
self.base_parser = Some(base);
}
}
}
Rc
implements DeRef
. The only real change is in the base struct construction.impl<T: BinaryOpParsingStrategy + BaseStructInitializable<Self>> FormulaParser<T>{
pub fn new(binary_op_parsing_strategy: T) -> Rc<FormulaParser<T>>{
let parser = FormulaParser {binary_op_parsing_strategy};
let mut parser_wrapper = Rc::new(parser);
let reference_for_strategy = Rc::clone(&parser_wrapper);
parser_wrapper.binary_op_parsing_strategy.initialize_base(reference_for_strategy);
parser_wrapper
}
}
Rc
cannot return a mutable reference to its contents and that Rust, by default, does not allow inner mutability for non-mutable references. This is very different to languages like C# that let you call any public method on any object you have a reference to.Rc
's documentation tells you how to solve this.Rc
, we have to explicitly tell Rust that the inner mutation is OK. This is done using the types Cell
and RefCell
.RefCell
in our example looks as follows. (You have to import std::cell::RefCell
.)pub struct BinaryOpParser<T>{
base_parser: Option<Rc<RefCell<T>>>,
}
pub trait BaseStructInitializable<T>{
fn initialize_base(&mut self, base: Rc<RefCell<T>>);
}
impl<T: FormulaParserProtectedInterface> BaseStructInitializable<T> for BinaryOpParser<T>{
fn initialize_base(&mut self, base: Rc<RefCell<T>>){
if self.base_parser.is_none(){
self.base_parser = Some(base);
}
}
}
std::cell::RefMut
.)impl<T: BinaryOpParsingStrategy + BaseStructInitializable<Self>> FormulaParser<T>{
pub fn new(binary_op_parsing_strategy: T) -> Rc<FormulaParser<RefCell<T>>>{
let parser = FormulaParser {binary_op_parsing_strategy};
let parser_cell = RefCell::new(parser);
let parser_wrapper = Rc::new(parser_cell);
let reference_for_strategy = Rc::clone(&parser_wrapper);
{
let mut strategy: RefMut<_> = parser_wrapper.borrow_mut();
strategy.binary_op_parsing_strategy.initialize_base(reference_for_strategy);
}
parser_wrapper
}
}
Rc<T>
to a weak Weak<T>
. This is sensible since the strategy really no longer needs to do anything when the base struct has been dropped.std::rc::Weak
.)pub struct BinaryOpParser<T>{
base_parser: Option<Weak<RefCell<T>>>,
}
pub trait BaseStructInitializable<T>{
fn initialize_base(&mut self, base: Weak<RefCell<T>>);
}
impl<T: FormulaParserProtectedInterface> BaseStructInitializable<T> for BinaryOpParser<T>{
fn initialize_base(&mut self, base: Weak<RefCell<T>>){
if self.base_parser.is_none(){
self.base_parser = Some(base);
}
}
}
std::cell::RefMut
.)impl<T: BinaryOpParsingStrategy + BaseStructInitializable<Self>> FormulaParser<T>{
pub fn new(binary_op_parsing_strategy: T) -> Rc<FormulaParser<RefCell<T>>>{
let parser = FormulaParser {binary_op_parsing_strategy};
let parser_cell = RefCell::new(parser);
let parser_wrapper = Rc::new(parser_cell);
let reference_for_strategy = Rc::downgrade(&parser_wrapper);
{
let mut strategy: RefMut<_> = parser_wrapper.borrow_mut();
strategy.binary_op_parsing_strategy.initialize_base(reference_for_strategy);
}
parser_wrapper
}
}
downgrade
instead of clone
. This does not increase the reference count and will allow the base struct to be dropped once the reference count of the Rc
we return from new
reaches zero.impl<T: FormulaParserProtectedInterface> BinaryOpParsingStrategy for BinaryOpParser<T>{
fn parse_binary_op(&self, operator_index: usize, tokenstream: &[Token])-> Option<FormulaContext>{
let base_wrapper = self.base_parser.as_ref()?;
let base_cell = base_wrapper.upgrade()?;
let base_parser = base_cell.borrow();
//Do something
let operand = self.base_parser.parse_operand(operand_index, tokenstream);
//Do more stuff
}
}
Rc<RefCell<T>>
although the caller really wants a T
.FormulaParserContainer
to keep things consistent here. However, from the point of view of the caller, it would be more sensible to rename the original FormulaParser
to FormulaParserImpl
and reuse the original name for the wrapper.pub struct FormulaParserContainer<T>{
parser_impl: Rc<RefCell<FormulaParser<T>>>,
}
impl<T: BinaryOpParsingStrategy + BaseStructInitializable<Self>> FormulaParserContainer<T>{
pub fn new(binary_op_parsing_strategy: T) -> FormulaParserContainer<T>{
let parser_impl = FormulaParser::new(binary_op_parsing_strategy);
FormulaParserContainer {parser_impl};
}
pub fn parse(&self, index: usize, tokenstream: &[Token]) -> Option<FormulaContext>{
let inner = self.parser_impl.borrow();
inner.parse(index, tokenstream)
}
}
Rc<T>
nor RefCell<T>
is thread-safe. So, if you want to share the emulated abstract class between threads, you will have to look into the alternatives/ways to handle this in the module-level documentation for these two types.pub trait BinaryOpParsingStrategy<T: FormulaParserProtectedInterface>{
fn parse_binary_op(&self, base: &T, operator_index: usize, tokenstream: &[Token])-> Option<FormulaContext>;
}
FormulaParserProtectedInterface
is the trait we defined in the section Some Logistics to make the protected methods on the base struct available to the strategy emulating the derived class.pub struct BinaryOpParser{}
impl<T: FormulaParserProtectedInterface> BinaryOpParsingStrategy<T> for BinaryOpParser{
fn parse_binary_op(&self, base: &T, operator_index: usize, tokenstream: &[Token])-> Option<FormulaContext>{
//Do something
let operand = base.parse_operand(operand_index, tokenstream);
//Do more stuff
}
}
impl BinaryOpParser{
pub fn new() -> BinaryOpParser{
BinaryOpParser {}
}
}
impl<T: BinaryOpParsingStrategy<Self>> FormulaParser<T>{
pub fn new(binary_op_parsing_strategy: T) -> FormulaParser<T>{
FormulaParser {binary_op_parsing_strategy}
}
}
pub struct FormulaParser<T>{
binary_op_parsing_strategy: T,
}
impl<T: BinaryOpParsingStrategy<Self>> FormulaParser<T>{
pub fn parse(&self, index: usize, tokenstream: &[Token]) -> Option<FormulaContext>{
//Do something
let binary_expression = self.parse_binary_op(operator_index, tokenstream)?;
//Do more stuff
}
fn parse_operand(&self, index: usize, tokenstream: &[Token]) -> Option<FormulaExpression>
{
//Do something
}
fn parse_binary_op(&self, operator_index: usize, tokenstream: &[Token])-> Option<FormulaContext>{
self.binary_op_parsing_strategy.parse_binary_op(self, operator_index, tokenstream)
}
}
Option
from the derived class.