Add support for callable form validation rules
diff --git a/system/database/drivers/mysqli/mysqli_driver.php b/system/database/drivers/mysqli/mysqli_driver.php
index 6ab3049..c5565f6 100644
--- a/system/database/drivers/mysqli/mysqli_driver.php
+++ b/system/database/drivers/mysqli/mysqli_driver.php
@@ -163,7 +163,7 @@
 	 */
 	protected function _db_set_charset($charset)
 	{
-		return @$this->conn_id->set_charset($charset);
+		return $this->conn_id->set_charset($charset);
 	}
 
 	// --------------------------------------------------------------------
diff --git a/system/libraries/Form_validation.php b/system/libraries/Form_validation.php
index 0c4f949..0a80888 100644
--- a/system/libraries/Form_validation.php
+++ b/system/libraries/Form_validation.php
@@ -144,7 +144,7 @@
 	 * Set Rules
 	 *
 	 * This function takes an array of field names and validation
-	 * rules as input, any custom error messages, validates the info, 
+	 * rules as input, any custom error messages, validates the info,
 	 * and stores it
 	 *
 	 * @param	mixed	$field
@@ -153,7 +153,7 @@
 	 * @param	array	$errors
 	 * @return	CI_Form_validation
 	 */
-	public function set_rules($field, $label = '', $rules = '', $errors = array())
+	public function set_rules($field, $label = '', $rules = array(), $errors = array())
 	{
 		// No reason to set rules if we have no POST data
 		// or a validation array has not been specified
@@ -187,26 +187,32 @@
 			return $this;
 		}
 
-		// Convert an array of rules to a string
-		if (is_array($rules))
-		{
-			$rules = implode('|', $rules);
-		}
-
 		// No fields? Nothing to do...
-		if ( ! is_string($field) OR ! is_string($rules) OR $field === '')
+		if ( ! is_string($field) OR $field === '')
 		{
 			return $this;
 		}
+		elseif ( ! is_array($rules))
+		{
+			// BC: Convert pipe-separated rules string to an array
+			if (is_string($rules))
+			{
+				$rules = explode('|', $rules);
+			}
+			else
+			{
+				return $this;
+			}
+		}
 
 		// If the field label wasn't passed we use the field name
 		$label = ($label === '') ? $field : $label;
 
 		// Is the field name an array? If it is an array, we break it apart
 		// into its components so that we can fetch the corresponding POST data later
-		$indexes = array();
-		if (preg_match_all('/\[(.*?)\]/', $field, $matches))
+		if (($is_array = (bool) preg_match_all('/\[(.*?)\]/', $field, $matches)) === TRUE)
 		{
+			$indexes = array();
 			sscanf($field, '%[^[][', $indexes[0]);
 
 			for ($i = 0, $c = count($matches[0]); $i < $c; $i++)
@@ -219,10 +225,6 @@
 
 			$is_array = TRUE;
 		}
-		else
-		{
-			$is_array	= FALSE;
-		}
 
 		// Build our master array
 		$this->_field_data[$field] = array(
@@ -468,7 +470,7 @@
 				continue;
 			}
 
-			$this->_execute($row, explode('|', $row['rules']), $this->_field_data[$field]['postdata']);
+			$this->_execute($row, $row['rules'], $this->_field_data[$field]['postdata']);
 		}
 
 		// Did we end up with any errors?
@@ -591,19 +593,33 @@
 		if ( ! in_array('required', $rules) && ($postdata === NULL OR $postdata === ''))
 		{
 			// Before we bail out, does the rule contain a callback?
-			if (preg_match('/(callback_\w+(\[.*?\])?)/', implode(' ', $rules), $match))
+			foreach ($rules as &$rule)
 			{
-				$callback = TRUE;
-				$rules = array(1 => $match[1]);
+				if (is_string($rule))
+				{
+					if (strncmp($rule, 'callback_', 9) === 0)
+					{
+						$callback = TRUE;
+						$rules = array(1 => $rule);
+						break;
+					}
+				}
+				elseif (is_callable($rule))
+				{
+					$callback = TRUE;
+					$rules = array(1 => $rule);
+					break;
+				}
 			}
-			else
+
+			if ( ! $callback)
 			{
 				return;
 			}
 		}
 
 		// Isset Test. Typically this rule will only apply to checkboxes.
-		if (($postdata === NULL OR $postdata === '') && $callback === FALSE)
+		if (($postdata === NULL OR $postdata === '') && ! $callback)
 		{
 			if (in_array('isset', $rules, TRUE) OR in_array('required', $rules))
 			{
@@ -668,39 +684,55 @@
 				// somebody messing with the form on the client side, so we'll just consider
 				// it an empty field
 				$postdata = is_array($this->_field_data[$row['field']]['postdata'])
-						? NULL
-						: $this->_field_data[$row['field']]['postdata'];
+					? NULL
+					: $this->_field_data[$row['field']]['postdata'];
 			}
 
 			// Is the rule a callback?
-			$callback = FALSE;
-			if (strpos($rule, 'callback_') === 0)
+			$callback = $callable = FALSE;
+			if (is_string($rule))
 			{
-				$rule = substr($rule, 9);
-				$callback = TRUE;
+				if (strpos($rule, 'callback_') === 0)
+				{
+					$rule = substr($rule, 9);
+					$callback = TRUE;
+				}
+			}
+			elseif (is_callable($rule))
+			{
+				$callable = TRUE;
 			}
 
 			// Strip the parameter (if exists) from the rule
 			// Rules can contain a parameter: max_length[5]
 			$param = FALSE;
-			if (preg_match('/(.*?)\[(.*)\]/', $rule, $match))
+			if ( ! $callable && preg_match('/(.*?)\[(.*)\]/', $rule, $match))
 			{
 				$rule = $match[1];
 				$param = $match[2];
 			}
 
 			// Call the function that corresponds to the rule
-			if ($callback === TRUE)
+			if ($callback OR $callable)
 			{
-				if ( ! method_exists($this->CI, $rule))
+				if ($callback)
 				{
-					log_message('debug', 'Unable to find callback validation rule: '.$rule);
-					$result = FALSE;
+					if ( ! method_exists($this->CI, $rule))
+					{
+						log_message('debug', 'Unable to find callback validation rule: '.$rule);
+						$result = FALSE;
+					}
+					else
+					{
+						// Run the function and grab the result
+						$result = $this->CI->$rule($postdata, $param);
+					}
 				}
 				else
 				{
-					// Run the function and grab the result
-					$result = $this->CI->$rule($postdata, $param);
+					$result = is_array($rule)
+						? $rule[0]->{$rule[1]}($postdata, $param)
+						: $rule($postdata, $param);
 				}
 
 				// Re-assign the result to the master data array
@@ -725,6 +757,7 @@
 				// Users can use any native PHP function call that has one param.
 				if (function_exists($rule))
 				{
+					// Native PHP functions issue warnings if you pass them more parameters than they use
 					$result = ($param !== FALSE) ? $rule($postdata, $param) : $rule($postdata);
 
 					if ($_in_array === TRUE)
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index 423755b..36ddbc9 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -345,6 +345,7 @@
       -  :doc:`Language <libraries/language>` line keys must now be prefixed with **form_validation_**.
       -  Added rule **alpha_numeric_spaces**.
       -  Added support for custom error messages per field rule.
+      -  Added support for callable rules when they are passed as an array.
 
    -  :doc:`Caching Library <libraries/caching>` changes include:
 
diff --git a/user_guide_src/source/libraries/form_validation.rst b/user_guide_src/source/libraries/form_validation.rst
index 988d6fa..e516309 100644
--- a/user_guide_src/source/libraries/form_validation.rst
+++ b/user_guide_src/source/libraries/form_validation.rst
@@ -474,6 +474,42 @@
 	boolean TRUE/FALSE it is assumed that the data is your newly processed
 	form data.
 
+Callable: Use anything as a rule
+================================
+
+If callback rules aren't good enough for you (for example, because they are
+limited to your controller), don't get disappointed, there's one more way
+to create custom rules: anything that ``is_callable()`` would return TRUE for.
+
+Consider the following example::
+
+	$this->form_validation->set_rules(
+		'username', 'Username',
+		array(
+			'required',
+			array($this->users_model, 'valid_username')
+		)
+	);
+
+The above code would use the ``valid_username()`` method from your
+``Users_model`` object.
+
+This is just an example of course, and callbacks aren't limited to models.
+You can use any object/method that accepts the field value as its' first
+parameter. Or if you're running PHP 5.3+, you can also use an anonymous
+function:
+
+	$this->form_validation->set_rules(
+		'username', 'Username',
+		array(
+			'required',
+			function($value)
+			{
+				// Check $value and return TRUE/FALSE
+			}
+		)
+	);
+
 .. _setting-error-messages:
 
 Setting Error Messages
@@ -491,7 +527,7 @@
 some particular rule, use the set_rules() method::
 
 	$this->form_validation->set_rules('field_name', 'Field Label', 'rule1|rule2|rule3',
-		array('rule2'	=> 'Error Message on rule2 for this field_name')
+		array('rule2' => 'Error Message on rule2 for this field_name')
 	);
 
 Where rule corresponds to the name of a particular rule, and Error